UNPKG

@robotical/ricjs

Version:

Javascript/TS library for Robotical RIC

440 lines 22.6 kB
"use strict"; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // RICJS // Communications Library // // Rob Dobson & Chris Greening 2020-2022 // (C) 2020-2022 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// Object.defineProperty(exports, "__esModule", { value: true }); exports.RICMsgResultCode = exports.RICCommsMsgProtocol = exports.RICCommsMsgTypeCode = exports.RICRESTElemCode = void 0; const tslib_1 = require("tslib"); const RICMsgTrackInfo_1 = require("./RICMsgTrackInfo"); const RICLog_1 = tslib_1.__importDefault(require("./RICLog")); const RICUtils_1 = tslib_1.__importDefault(require("./RICUtils")); const RICROSSerial_1 = require("./RICROSSerial"); const RICProtocolDefs_1 = require("./RICProtocolDefs"); const RICMiniHDLC_1 = tslib_1.__importDefault(require("./RICMiniHDLC")); // Protocol enums var RICRESTElemCode; (function (RICRESTElemCode) { RICRESTElemCode[RICRESTElemCode["RICREST_ELEM_CODE_URL"] = 0] = "RICREST_ELEM_CODE_URL"; RICRESTElemCode[RICRESTElemCode["RICREST_ELEM_CODE_CMDRESPJSON"] = 1] = "RICREST_ELEM_CODE_CMDRESPJSON"; RICRESTElemCode[RICRESTElemCode["RICREST_ELEM_CODE_BODY"] = 2] = "RICREST_ELEM_CODE_BODY"; RICRESTElemCode[RICRESTElemCode["RICREST_ELEM_CODE_COMMAND_FRAME"] = 3] = "RICREST_ELEM_CODE_COMMAND_FRAME"; RICRESTElemCode[RICRESTElemCode["RICREST_ELEM_CODE_FILEBLOCK"] = 4] = "RICREST_ELEM_CODE_FILEBLOCK"; })(RICRESTElemCode = exports.RICRESTElemCode || (exports.RICRESTElemCode = {})); var RICCommsMsgTypeCode; (function (RICCommsMsgTypeCode) { RICCommsMsgTypeCode[RICCommsMsgTypeCode["MSG_TYPE_COMMAND"] = 0] = "MSG_TYPE_COMMAND"; RICCommsMsgTypeCode[RICCommsMsgTypeCode["MSG_TYPE_RESPONSE"] = 1] = "MSG_TYPE_RESPONSE"; RICCommsMsgTypeCode[RICCommsMsgTypeCode["MSG_TYPE_PUBLISH"] = 2] = "MSG_TYPE_PUBLISH"; RICCommsMsgTypeCode[RICCommsMsgTypeCode["MSG_TYPE_REPORT"] = 3] = "MSG_TYPE_REPORT"; })(RICCommsMsgTypeCode = exports.RICCommsMsgTypeCode || (exports.RICCommsMsgTypeCode = {})); var RICCommsMsgProtocol; (function (RICCommsMsgProtocol) { RICCommsMsgProtocol[RICCommsMsgProtocol["MSG_PROTOCOL_ROSSERIAL"] = 0] = "MSG_PROTOCOL_ROSSERIAL"; RICCommsMsgProtocol[RICCommsMsgProtocol["MSG_PROTOCOL_RESERVED_1"] = 1] = "MSG_PROTOCOL_RESERVED_1"; RICCommsMsgProtocol[RICCommsMsgProtocol["MSG_PROTOCOL_RICREST"] = 2] = "MSG_PROTOCOL_RICREST"; })(RICCommsMsgProtocol = exports.RICCommsMsgProtocol || (exports.RICCommsMsgProtocol = {})); // Message results var RICMsgResultCode; (function (RICMsgResultCode) { RICMsgResultCode[RICMsgResultCode["MESSAGE_RESULT_TIMEOUT"] = 0] = "MESSAGE_RESULT_TIMEOUT"; RICMsgResultCode[RICMsgResultCode["MESSAGE_RESULT_OK"] = 1] = "MESSAGE_RESULT_OK"; RICMsgResultCode[RICMsgResultCode["MESSAGE_RESULT_FAIL"] = 2] = "MESSAGE_RESULT_FAIL"; RICMsgResultCode[RICMsgResultCode["MESSAGE_RESULT_UNKNOWN"] = 3] = "MESSAGE_RESULT_UNKNOWN"; })(RICMsgResultCode = exports.RICMsgResultCode || (exports.RICMsgResultCode = {})); class RICMsgHandler { // Constructor constructor(commsStats, addOnManager) { // Message numbering and tracking this._currentMsgNumber = 1; this._currentMsgHandle = 1; this._msgTrackInfos = new Array(RICMsgTrackInfo_1.RICMsgTrackInfo.MAX_MSG_NUM + 1); this._msgTrackTimerMs = 50; this._msgTrackLastCheckIdx = 0; // report message callback dictionary. Add a callback to subscribe to report messages this._reportMsgCallbacks = new Map(); // Interface to inform of message results this._msgResultHandler = null; // Interface to send messages this._msgSender = null; this._commsStats = commsStats; this._addOnManager = addOnManager; RICLog_1.default.debug('RICMsgHandler constructor'); // Message tracking for (let i = 0; i < this._msgTrackInfos.length; i++) { this._msgTrackInfos[i] = new RICMsgTrackInfo_1.RICMsgTrackInfo(); } // Timer for checking messages setTimeout(async () => { this._onMsgTrackTimer(true); }, this._msgTrackTimerMs); // HDLC used to encode/decode the RICREST protocol this._miniHDLC = new RICMiniHDLC_1.default(); this._miniHDLC.setOnRxFrame(this._onHDLCFrameDecode.bind(this)); } registerForResults(msgResultHandler) { this._msgResultHandler = msgResultHandler; } registerMsgSender(RICMessageSender) { this._msgSender = RICMessageSender; } handleNewRxMsg(rxMsg) { this._miniHDLC.addRxBytes(rxMsg); // RICLog.verbose(`handleNewRxMsg len ${rxMsg.length} ${RICUtils.bufferToHex(rxMsg)}`) } reportMsgCallbacksSet(callbackName, callback) { this._reportMsgCallbacks.set(callbackName, callback); } reportMsgCallbacksDelete(callbackName) { this._reportMsgCallbacks.delete(callbackName); } _onHDLCFrameDecode(rxMsg) { // Add to stats this._commsStats.msgRx(); // Validity if (rxMsg.length < RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS) { this._commsStats.msgTooShort(); return; } // RICLog.verbose(`_onHDLCFrameDecode len ${rxMsg.length}`); // Decode the RICFrame header const rxMsgNum = rxMsg[RICProtocolDefs_1.RICSERIAL_MSG_NUM_POS] & 0xff; const rxProtocol = rxMsg[RICProtocolDefs_1.RICSERIAL_PROTOCOL_POS] & 0x3f; const rxMsgType = (rxMsg[RICProtocolDefs_1.RICSERIAL_PROTOCOL_POS] >> 6) & 0x03; // Decode payload if (rxProtocol == RICProtocolDefs_1.PROTOCOL_RICREST) { RICLog_1.default.verbose(`_onHDLCFrameDecode RICREST rx msgNum ${rxMsgNum} msgDirn ${rxMsgType} ${RICUtils_1.default.bufferToHex(rxMsg)}`); // Extract payload const ricRestElemCode = rxMsg[RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS + RICProtocolDefs_1.RICREST_REST_ELEM_CODE_POS] & 0xff; if (ricRestElemCode == RICRESTElemCode.RICREST_ELEM_CODE_URL || ricRestElemCode == RICRESTElemCode.RICREST_ELEM_CODE_CMDRESPJSON || ricRestElemCode == RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME) { // These are all text-based messages const restStr = RICUtils_1.default.getStringFromBuffer(rxMsg, RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS + RICProtocolDefs_1.RICREST_HEADER_PAYLOAD_POS, rxMsg.length - RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS - RICProtocolDefs_1.RICREST_HEADER_PAYLOAD_POS - 1); RICLog_1.default.verbose(`_onHDLCFrameDecode RICREST rx elemCode ${ricRestElemCode} ${restStr}`); // Check message types if (rxMsgType == RICCommsMsgTypeCode.MSG_TYPE_RESPONSE) { // Handle response messages this._handleResponseMessages(restStr, rxMsgNum); } else if (rxMsgType == RICCommsMsgTypeCode.MSG_TYPE_REPORT) { // Handle report messages this._handleReportMessages(restStr); } } else { const binMsgLen = rxMsg.length - RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS - RICProtocolDefs_1.RICREST_HEADER_PAYLOAD_POS; RICLog_1.default.debug(`_onHDLCFrameDecode RICREST rx binary message elemCode ${ricRestElemCode} len ${binMsgLen}`); } } else if (rxProtocol == RICCommsMsgProtocol.MSG_PROTOCOL_ROSSERIAL) { // Extract ROSSerial messages - decoded messages returned via _msgResultHandler RICROSSerial_1.RICROSSerial.decode(rxMsg, RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS, this._msgResultHandler, this._commsStats, this._addOnManager); } else { RICLog_1.default.warn(`_onHDLCFrameDecode unsupported protocol ${rxProtocol}`); } } _handleResponseMessages(restStr, rxMsgNum) { try { let msgRsltCode = RICMsgResultCode.MESSAGE_RESULT_UNKNOWN; const msgRsltJsonObj = JSON.parse(restStr); if ('rslt' in msgRsltJsonObj) { const rsltStr = msgRsltJsonObj.rslt.toLowerCase(); if (rsltStr === 'ok') { RICLog_1.default.verbose(`_handleResponseMessages RICREST rslt Ok ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()} resp ${msgRsltJsonObj.rslt}`); msgRsltCode = RICMsgResultCode.MESSAGE_RESULT_OK; } else if (rsltStr === 'fail') { msgRsltCode = RICMsgResultCode.MESSAGE_RESULT_FAIL; RICLog_1.default.warn(`_handleResponseMessages RICREST rslt fail ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()} resp ${restStr}`); } else { RICLog_1.default.warn(`_handleResponseMessages RICREST rslt not recognized ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()}resp ${restStr}`); } } else { RICLog_1.default.warn(`_handleResponseMessages RICREST response doesn't contain rslt ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()}resp ${restStr}`); } // Handle matching of request and response this.msgTrackingRxRespMsg(rxMsgNum, msgRsltCode, msgRsltJsonObj); } catch (excp) { if (excp instanceof Error) { RICLog_1.default.warn(`_handleResponseMessages Failed to parse JSON ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()} JSON STR ${restStr} resp ${excp.toString()}`); } } } _handleReportMessages(restStr) { try { const reportMsg = JSON.parse(restStr); reportMsg.timeReceived = Date.now(); RICLog_1.default.debug(`_handleReportMessages ${JSON.stringify(reportMsg)}`); this._reportMsgCallbacks.forEach((callback) => callback(reportMsg)); } catch (excp) { if (excp instanceof Error) { RICLog_1.default.warn(`_handleReportMessages Failed to parse JSON report ${excp.toString()}`); } } } async sendRICRESTURL(cmdStr, msgTimeoutMs = undefined) { // Send return this.sendRICREST(cmdStr, RICRESTElemCode.RICREST_ELEM_CODE_URL, msgTimeoutMs); } async sendRICRESTCmdFrame(cmdStr, msgTimeoutMs = undefined) { // Send return this.sendRICREST(cmdStr, RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME, msgTimeoutMs); } async sendRICREST(cmdStr, ricRESTElemCode, msgTimeoutMs = undefined) { // Put cmdStr into buffer const cmdStrTerm = new Uint8Array(cmdStr.length + 1); RICUtils_1.default.addStringToBuffer(cmdStrTerm, cmdStr, 0); cmdStrTerm[cmdStrTerm.length - 1] = 0; // Send return this.sendRICRESTBytes(cmdStrTerm, ricRESTElemCode, true, msgTimeoutMs); } async sendRICRESTBytes(cmdBytes, ricRESTElemCode, withResponse, msgTimeoutMs = undefined) { // Form message const cmdMsg = new Uint8Array(cmdBytes.length + RICProtocolDefs_1.RICREST_HEADER_PAYLOAD_POS); cmdMsg[RICProtocolDefs_1.RICREST_REST_ELEM_CODE_POS] = ricRESTElemCode; cmdMsg.set(cmdBytes, RICProtocolDefs_1.RICREST_HEADER_PAYLOAD_POS); // Send return this.sendMsgAndWaitForReply(cmdMsg, RICCommsMsgTypeCode.MSG_TYPE_COMMAND, RICCommsMsgProtocol.MSG_PROTOCOL_RICREST, withResponse, msgTimeoutMs); } async sendMsgAndWaitForReply(msgPayload, msgDirection, msgProtocol, withResponse, msgTimeoutMs) { // Check there is a sender if (!this._msgSender) { throw new Error('sendMsgAndWaitForReply failed no sender'); } // Frame the message const framedMsg = this.frameCommsMsg(msgPayload, msgDirection, msgProtocol, true); if (!framedMsg) { throw new Error('sendMsgAndWaitForReply failed to frame message'); } // Debug // RICLog.verbose( // `sendMsgAndWaitForReply ${RICUtils.bufferToHex(framedMsg)}`, // ); // Return a promise that will be resolved when a reply is received or timeout occurs const promise = new Promise((resolve, reject) => { // Update message tracking this.msgTrackingTxCmdMsg(framedMsg, withResponse, msgTimeoutMs, resolve, reject); this._currentMsgHandle++; }); return promise; } frameCommsMsg(msgPayload, msgDirection, msgProtocol, isNumbered) { // Header const msgBuf = new Uint8Array(msgPayload.length + RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS); msgBuf[0] = isNumbered ? this._currentMsgNumber & 0xff : 0; msgBuf[1] = (msgDirection << 6) + msgProtocol; // Payload msgBuf.set(msgPayload, RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS); // Wrap into HDLC return this._miniHDLC.encode(msgBuf); } msgTrackingTxCmdMsg(msgFrame, withResponse, msgTimeoutMs, resolve, reject) { // Record message re-use of number if (this._msgTrackInfos[this._currentMsgNumber].msgOutstanding) { this._commsStats.recordMsgNumCollision(); } // Set tracking info this._msgTrackInfos[this._currentMsgNumber].set(true, msgFrame, withResponse, this._currentMsgHandle, msgTimeoutMs, resolve, reject); // Debug RICLog_1.default.verbose(`msgTrackingTxCmdMsg msgNum ${this._currentMsgNumber} msg ${RICUtils_1.default.bufferToHex(msgFrame)} sanityCheck ${this._msgTrackInfos[this._currentMsgNumber].msgOutstanding}`); // RICLog.debug( // `msgTrackingTxCmdMsg msgNum ${this._currentMsgNumber} msgLen ${msgFrame.length}}`, // ); // Stats this._commsStats.msgTx(); // Bump msg number if (this._currentMsgNumber == RICMsgTrackInfo_1.RICMsgTrackInfo.MAX_MSG_NUM) { this._currentMsgNumber = 1; } else { this._currentMsgNumber++; } } msgTrackingRxRespMsg(msgNum, msgRsltCode, msgRsltJsonObj) { // Check message number if (msgNum == 0) { // Callback on unnumbered message if (this._msgResultHandler !== null) this._msgResultHandler.onRxUnnumberedMsg(msgRsltJsonObj); return; } if (msgNum > RICMsgTrackInfo_1.RICMsgTrackInfo.MAX_MSG_NUM) { RICLog_1.default.warn('msgTrackingRxRespMsg msgNum > 255'); return; } if (!this._msgTrackInfos[msgNum].msgOutstanding) { RICLog_1.default.warn(`msgTrackingRxRespMsg unmatched msgNum ${msgNum}`); this._commsStats.recordMsgNumUnmatched(); return; } // Handle message RICLog_1.default.verbose(`msgTrackingRxRespMsg Message response received msgNum ${msgNum}`); this._commsStats.recordMsgResp(Date.now() - this._msgTrackInfos[msgNum].msgSentMs); this._msgCompleted(msgNum, msgRsltCode, msgRsltJsonObj); } _msgCompleted(msgNum, msgRsltCode, msgRsltObj) { // Lookup message in tracking const msgHandle = this._msgTrackInfos[msgNum].msgHandle; this._msgTrackInfos[msgNum].msgOutstanding = false; // Check if message result handler should be informed if (this._msgResultHandler !== null) { this._msgResultHandler.onRxReply(msgHandle, msgRsltCode, msgRsltObj); } // Handle reply // if (msgRsltCode === RICMsgResultCode.MESSAGE_RESULT_OK) { const resolve = this._msgTrackInfos[msgNum].resolve; if (resolve) { // eslint-disable-next-line @typescript-eslint/no-explicit-any RICLog_1.default.debug(`_msgCompleted resolve ${msgRsltCode} ${JSON.stringify(msgRsltObj)}`); resolve(msgRsltObj); } // } else { // const reject = this._msgTrackInfos[msgNum].reject; // if (reject) { // // eslint-disable-next-line @typescript-eslint/no-explicit-any // try { // RICLog.debug(`_msgCompleted reject rsltCode ${msgRsltCode}`); // // (reject as any)(new Error(`Message failed msgNum ${msgNum} rslt ${msgRsltCode}`)); // } catch (excp: unknown) { // RICLog.warn(`_msgCompleted reject ${excp}`); // } // } // } // No longer waiting for reply this._msgTrackInfos[msgNum].resolve = null; this._msgTrackInfos[msgNum].reject = null; } // Check message timeouts async _onMsgTrackTimer(chainRecall) { if (this._msgSender !== null) { // Handle message tracking for (let loopIdx = 0; loopIdx < this._msgTrackInfos.length; loopIdx++) { // Index to check const checkIdx = this._msgTrackLastCheckIdx; this._msgTrackLastCheckIdx = (checkIdx + 1) % this._msgTrackInfos.length; // Check if message is outstanding if (!this._msgTrackInfos[checkIdx].msgOutstanding) continue; // Get message timeout and ensure valid let msgTimeoutMs = this._msgTrackInfos[checkIdx].msgTimeoutMs; if (msgTimeoutMs === undefined) { msgTimeoutMs = RICMsgTrackInfo_1.RICMsgTrackInfo.MSG_RESPONSE_TIMEOUT_MS; } // Check for timeout (or never sent) if ((this._msgTrackInfos[checkIdx].retryCount === 0) || (Date.now() > this._msgTrackInfos[checkIdx].msgSentMs + msgTimeoutMs)) { // Debug RICLog_1.default.debug(`msgTrackTimer msgNum ${checkIdx} ${this._msgTrackInfos[checkIdx].retryCount === 0 ? 'first send' : 'timeout - retrying'}`); // RICLog.verbose(`msgTrackTimer msg ${RICUtils.bufferToHex(this._msgTrackInfos[i].msgFrame)}`); // Handle timeout (or first send) if (this._msgTrackInfos[checkIdx].retryCount < RICMsgTrackInfo_1.RICMsgTrackInfo.MSG_RETRY_COUNT) { this._msgTrackInfos[checkIdx].retryCount++; try { // Send the message if (!await this._msgSender.sendTxMsg(this._msgTrackInfos[checkIdx].msgFrame, this._msgTrackInfos[checkIdx].withResponse)) { RICLog_1.default.warn(`msgTrackTimer Message send failed msgNum ${checkIdx}`); this._msgCompleted(checkIdx, RICMsgResultCode.MESSAGE_RESULT_FAIL, null); this._commsStats.recordMsgNoConnection(); } // Message sent ok so break here break; } catch (error) { RICLog_1.default.warn(`Retry message failed ${error}`); } this._commsStats.recordMsgRetry(); this._msgTrackInfos[checkIdx].msgSentMs = Date.now(); } else { RICLog_1.default.warn(`msgTrackTimer TIMEOUT msgNum ${checkIdx} after ${RICMsgTrackInfo_1.RICMsgTrackInfo.MSG_RETRY_COUNT} retries`); this._msgCompleted(checkIdx, RICMsgResultCode.MESSAGE_RESULT_TIMEOUT, null); this._commsStats.recordMsgTimeout(); } } } } // Call again if required if (chainRecall) { setTimeout(async () => { this._onMsgTrackTimer(true); }, this._msgTrackTimerMs); } } encodeFileStreamBlock(blockContents, blockStart, streamID) { // Create entire message buffer (including protocol wrappers) const msgBuf = new Uint8Array(blockContents.length + 4 + RICProtocolDefs_1.RICREST_HEADER_PAYLOAD_POS + RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS); let msgBufPos = 0; // RICSERIAL protocol msgBuf[msgBufPos++] = 0; // not numbered msgBuf[msgBufPos++] = (RICCommsMsgTypeCode.MSG_TYPE_COMMAND << 6) + RICCommsMsgProtocol.MSG_PROTOCOL_RICREST; // RICREST protocol msgBuf[msgBufPos++] = RICRESTElemCode.RICREST_ELEM_CODE_FILEBLOCK; // Buffer header msgBuf[msgBufPos++] = streamID & 0xff; msgBuf[msgBufPos++] = (blockStart >> 16) & 0xff; msgBuf[msgBufPos++] = (blockStart >> 8) & 0xff; msgBuf[msgBufPos++] = blockStart & 0xff; // Copy block info msgBuf.set(blockContents, msgBufPos); return msgBuf; } async sendFileBlock(blockContents, blockStart) { const msgBuf = this.encodeFileStreamBlock(blockContents, blockStart, 0); // // Debug // RICLog.debug( // `sendFileBlock frameLen ${msgBuf.length} start ${blockStart} end ${blockEnd} len ${blockLen}`, // ); // Send try { // Send if (this._msgSender) { // Wrap into HDLC const framedMsg = this._miniHDLC.encode(msgBuf); // Send return this._msgSender.sendTxMsg(framedMsg, true); } } catch (error) { RICLog_1.default.warn(`RICMsgHandler sendFileBlock error${error}`); } return false; } async sendStreamBlock(blockContents, blockStart, streamID) { // Ensure any waiting messages are sent first await this._onMsgTrackTimer(false); // Encode message const msgBuf = this.encodeFileStreamBlock(blockContents, blockStart, streamID); // // Debug // RICLog.debug( // `sendStreamBlock frameLen ${msgBuf.length} start ${blockStart} end ${blockEnd} len ${blockLen}`, // ); // Send try { // Send if (this._msgSender) { // Wrap into HDLC const framedMsg = this._miniHDLC.encode(msgBuf); // Send return await this._msgSender.sendTxMsg(framedMsg, true); } } catch (error) { RICLog_1.default.warn(`RICMsgHandler sendStreamBlock error${error}`); } return false; } } exports.default = RICMsgHandler; //# sourceMappingURL=RICMsgHandler.js.map