UNPKG

@robotical/ricjs

Version:

Javascript/TS library for Robotical RIC

631 lines 26.3 kB
"use strict"; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // RICJS // Communications Library // // Rob Dobson & Chris Greening 2020-2022 // (C) 2020-2022 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const RICChannelWebBLE_1 = tslib_1.__importDefault(require("./RICChannelWebBLE")); const RICMsgHandler_1 = tslib_1.__importDefault(require("./RICMsgHandler")); const RICChannelWebSocket_1 = tslib_1.__importDefault(require("./RICChannelWebSocket")); const RICChannelWebSerial_1 = tslib_1.__importDefault(require("./RICChannelWebSerial")); const RICLEDPatternChecker_1 = tslib_1.__importDefault(require("./RICLEDPatternChecker")); const RICCommsStats_1 = tslib_1.__importDefault(require("./RICCommsStats")); const RICTypes_1 = require("./RICTypes"); const RICAddOnManager_1 = tslib_1.__importDefault(require("./RICAddOnManager")); const RICSystem_1 = tslib_1.__importDefault(require("./RICSystem")); const RICFileHandler_1 = tslib_1.__importDefault(require("./RICFileHandler")); const RICStreamHandler_1 = tslib_1.__importDefault(require("./RICStreamHandler")); const RICUtils_1 = tslib_1.__importDefault(require("./RICUtils")); const RICLog_1 = tslib_1.__importDefault(require("./RICLog")); const RICConnEvents_1 = require("./RICConnEvents"); const RICUpdateManager_1 = tslib_1.__importDefault(require("./RICUpdateManager")); const RICUpdateEvents_1 = require("./RICUpdateEvents"); const RICServoFaultDetector_1 = tslib_1.__importDefault(require("./RICServoFaultDetector")); class RICConnector { constructor() { // Channel this._ricChannel = null; // Channel connection method and locator this._channelConnMethod = ""; this._channelConnLocator = ""; // Comms stats this._commsStats = new RICCommsStats_1.default(); // Latest data from servos, IMU, etc this._ricStateInfo = new RICTypes_1.RICStateInfo(); // Add-on Manager this._addOnManager = new RICAddOnManager_1.default(); // Message handler this._ricMsgHandler = new RICMsgHandler_1.default(this._commsStats, this._addOnManager); // RICSystem this._ricSystem = new RICSystem_1.default(this._ricMsgHandler, this._addOnManager); // LED Pattern checker this._ledPatternChecker = new RICLEDPatternChecker_1.default(); this._ledPatternTimeoutMs = 10000; this._ledPatternRefreshTimer = null; // Subscription rate this._subscribeRateHz = 10; // Connection performance checker this._testConnPerfBlockSize = 500; this._testConnPerfNumBlocks = 20; this._connPerfRsltDelayMs = 4000; // Retry connection if lost this._retryIfLostEnabled = true; this._retryIfLostForSecs = 10; this._retryIfLostIsConnected = false; this._retryIfLostDisconnectTime = null; this._retryIfLostRetryDelayMs = 500; // File handler this._ricFileHandler = new RICFileHandler_1.default(this._ricMsgHandler, this._commsStats); // Stream handler this._ricStreamHandler = new RICStreamHandler_1.default(this._ricMsgHandler, this._commsStats, this); // Update manager this._ricUpdateManager = null; // Event listener this._onEventFn = null; // RICServoFaultDetector for detecting servo faults this.ricServoFaultDetector = new RICServoFaultDetector_1.default(this._ricMsgHandler, this._ricStateInfo); // Debug RICLog_1.default.debug('RICConnector starting up'); } setupUpdateManager(appVersion, appUpdateURL, firmwareBaseURL, fileDownloader) { // Setup update manager const firmwareTypeStrForMainFw = 'main'; this._ricUpdateManager = new RICUpdateManager_1.default(this._ricMsgHandler, this._ricFileHandler, this._ricSystem, this._onUpdateEvent.bind(this), firmwareTypeStrForMainFw, appVersion, fileDownloader, appUpdateURL, firmwareBaseURL, this._ricChannel); } configureFileHandler(fileBlockSize, batchAckSize) { this._ricFileHandler.setRequestedFileBlockSize(fileBlockSize); this._ricFileHandler.setRequestedBatchAckSize(batchAckSize); } setEventListener(onEventFn) { this._onEventFn = onEventFn; } isConnected() { // Check if connected const isConnected = this._retryIfLostIsConnected || (this._ricChannel ? this._ricChannel.isConnected() : false); return isConnected; } // Set retry channel mode setRetryConnectionIfLost(enableRetry, retryForSecs) { this._retryIfLostEnabled = enableRetry; this._retryIfLostForSecs = retryForSecs; if (!this._retryIfLostEnabled) { this._retryIfLostIsConnected = false; } RICLog_1.default.debug(`setRetryConnectionIfLost ${enableRetry} retry for ${retryForSecs}`); } getConnMethod() { return this._channelConnMethod; } getAddOnManager() { return this._addOnManager; } getRICSystem() { return this._ricSystem; } getRICState() { return this._ricStateInfo; } getCommsStats() { return this._commsStats; } getRICMsgHandler() { return this._ricMsgHandler; } getRICChannel() { return this._ricChannel; } getRICUpdateManager() { return this._ricUpdateManager; } getConnLocator() { return this._ricChannel ? this._ricChannel.getConnectedLocator() : null; } pauseConnection(pause = true) { if (this._ricChannel) this._ricChannel.pauseConnection(pause); } /** * Connect to a RIC * * @param {string} method - can be "WebBLE" or "WebSocket" * @param {string | object} locator - either a string (WebSocket URL) or an object (WebBLE) * @returns Promise<boolean> * */ async connect(method, locator) { // Ensure disconnected try { await this.disconnect(); } catch (err) { // Ignore } // Check connection method let connMethod = ""; if (method === 'WebBLE' && typeof locator === 'object') { // Create channel this._ricChannel = new RICChannelWebBLE_1.default(); connMethod = 'WebBLE'; } else if (((method === 'WebSocket') || (method === 'wifi')) && (typeof locator === 'string')) { // Create channel this._ricChannel = new RICChannelWebSocket_1.default(); connMethod = 'WebSocket'; } else if (((method === 'WebSerial'))) { this._ricChannel = new RICChannelWebSerial_1.default(); connMethod = 'WebSerial'; } RICLog_1.default.debug(`connecting with connMethod ${connMethod}`); // Check channel established let connOk = false; if (this._ricChannel !== null) { // Connection method and locator this._channelConnMethod = connMethod; this._channelConnLocator = locator; // Set message handler this._ricChannel.setMsgHandler(this._ricMsgHandler); this._ricChannel.setOnConnEvent(this.onConnEvent.bind(this)); // Message handling in and out this._ricMsgHandler.registerForResults(this); this._ricMsgHandler.registerMsgSender(this._ricChannel); // Connect try { // Event this.onConnEvent(RICConnEvents_1.RICConnEvent.CONN_CONNECTING_RIC); // Connect connOk = await this._connectToChannel(); } catch (err) { RICLog_1.default.error('RICConnector.connect - error: ' + err); } // Events if (connOk) { this.onConnEvent(RICConnEvents_1.RICConnEvent.CONN_CONNECTED_RIC); } else { // Failed Event this.onConnEvent(RICConnEvents_1.RICConnEvent.CONN_CONNECTION_FAILED); } // Subscribe for updates if required if (this._ricChannel.requiresSubscription()) { try { await this.subscribeForUpdates(true); RICLog_1.default.info(`connect subscribed for updates`); } catch (error) { RICLog_1.default.warn(`connect subscribe for updates failed ${error}`); } } // configure file handler this.configureFileHandler(this._ricChannel.fhFileBlockSize(), this._ricChannel.fhBatchAckSize()); } else { this._channelConnMethod = ""; } return connOk; } async disconnect() { // Disconnect this._retryIfLostIsConnected = false; if (this._ricChannel) { // await this.sendRICRESTMsg("bledisc", {}); this._ricChannel.disconnect(); } } // Mark: Tx Message handling ----------------------------------------------------------------------------------------- /** * * sendRICRESTMsg * @param commandName command API string * @param params parameters (simple name value pairs only) to parameterize trajectory * @returns Promise<RICOKFail> * */ async sendRICRESTMsg(commandName, params) { try { // Format the paramList as query string const paramEntries = Object.entries(params); let paramQueryStr = ''; for (const param of paramEntries) { if (paramQueryStr.length > 0) paramQueryStr += '&'; paramQueryStr += param[0] + '=' + param[1]; } // Format the url to send if (paramQueryStr.length > 0) commandName += '?' + paramQueryStr; return await this._ricMsgHandler.sendRICRESTURL(commandName); } catch (error) { RICLog_1.default.warn(`runCommand failed ${error}`); return new RICTypes_1.RICOKFail(); } } // Mark: Rx Message handling ----------------------------------------------------------------------------------------- onRxReply(msgHandle, msgRsltCode, msgRsltJsonObj) { RICLog_1.default.verbose(`onRxReply msgHandle ${msgHandle} rsltCode ${msgRsltCode} obj ${JSON.stringify(msgRsltJsonObj)}`); } onRxUnnumberedMsg(msgRsltJsonObj) { RICLog_1.default.verbose(`onRxUnnumberedMsg rsltCode obj ${JSON.stringify(msgRsltJsonObj)}`); // Inform the file handler if ('okto' in msgRsltJsonObj) { this._ricFileHandler.onOktoMsg(msgRsltJsonObj.okto); } else if ('sokto' in msgRsltJsonObj) { this._ricStreamHandler.onSoktoMsg(msgRsltJsonObj.sokto); } } // Mark: Published data handling ----------------------------------------------------------------------------------------- onRxOtherROSSerialMsg(topicID, payload) { RICLog_1.default.debug(`onRxOtherROSSerialMsg topicID ${topicID} payload ${RICUtils_1.default.bufferToHex(payload)}`); } onRxSmartServo(smartServos) { // RICLog.verbose(`onRxSmartServo ${JSON.stringify(smartServos)}`); this._ricStateInfo.smartServos = smartServos; this._ricStateInfo.smartServosValidMs = Date.now(); } onRxIMU(imuData) { // RICLog.verbose(`onRxIMU ${JSON.stringify(imuData)}`); this._ricStateInfo.imuData = imuData; this._ricStateInfo.imuDataValidMs = Date.now(); } onRxMagneto(magnetoData) { // RICLog.verbose(`onRxMagneto ${JSON.stringify(magnetoData)}`); this._ricStateInfo.magnetoData = magnetoData; this._ricStateInfo.magnetoDataValidMs = Date.now(); } onRxPowerStatus(powerStatus) { // RICLog.verbose(`onRxPowerStatus ${JSON.stringify(powerStatus)}`); this._ricStateInfo.power = powerStatus; this._ricStateInfo.powerValidMs = Date.now(); } onRxAddOnPub(addOnInfo) { // RICLog.verbose(`onRxAddOnPub ${JSON.stringify(addOnInfo)}`); this._ricStateInfo.addOnInfo = addOnInfo; this._ricStateInfo.addOnInfoValidMs = Date.now(); } onRobotStatus(robotStatus) { // RICLog.verbose(`onRobotStatus ${JSON.stringify(robotStatus)}`); this._ricStateInfo.robotStatus = robotStatus; this._ricStateInfo.robotStatusValidMs = Date.now(); } getRICStateInfo() { return this._ricStateInfo; } // Mark: Check correct RIC ----------------------------------------------------------------------------------------- /** * Start checking correct RIC using LED pattern * * @param {string} ricToConnectTo - RIC to connect to * @return boolean - true if started ok * */ async checkCorrectRICStart(ricLedLcdColours) { // Set colour pattern checker colours const randomColours = this._ledPatternChecker.setup(ricLedLcdColours); // Start timer to repeat checking LED pattern RICLog_1.default.debug(`checkCorrectRICStart: starting LED pattern checker`); if (!await this._checkCorrectRICRefreshLEDs()) { return false; } // Event this.onConnEvent(RICConnEvents_1.RICConnEvent.CONN_VERIFYING_CORRECT_RIC, randomColours); // Start timer to repeat sending of LED pattern // This is because RIC's LED pattern override times out after a while // so has to be refreshed periodically this._ledPatternRefreshTimer = setInterval(async () => { RICLog_1.default.verbose(`checkCorrectRICStart: loop`); if (!this._checkCorrectRICRefreshLEDs()) { RICLog_1.default.debug('checkCorrectRICStart no longer active - clearing timer'); this._clearLedPatternRefreshTimer(); } }, this._ledPatternTimeoutMs / 2.1); return true; } /** * Stop checking correct RIC * * @return void * */ async checkCorrectRICStop(confirmCorrectRIC) { // Stop refreshing LED pattern on RIC this._clearLedPatternRefreshTimer(); // Stop the LED pattern checker if connected if (this.isConnected()) { await this._ledPatternChecker.clearRICColors(this._ricMsgHandler); } // Check correct if (!confirmCorrectRIC) { // Event this.onConnEvent(RICConnEvents_1.RICConnEvent.CONN_REJECTED_RIC); // Indicate as rejected if we're not connected or if user didn't confirm return false; } // Event this.onConnEvent(RICConnEvents_1.RICConnEvent.CONN_VERIFIED_CORRECT_RIC); return true; } /** * Refresh LED pattern on RIC * * @return boolean - true if checking still active * */ async _checkCorrectRICRefreshLEDs() { // Check LED pattern is active if (!this._ledPatternChecker.isActive()) { return false; } // Check connected RICLog_1.default.debug(`_verificationRepeat getting isConnected`); if (!this.isConnected()) { console.warn('_verificationRepeat not connected'); return false; } // Repeat the LED pattern (RIC times out the LED override after ~10 seconds) RICLog_1.default.debug(`_verificationRepeat setting pattern`); return await this._ledPatternChecker.setRICColors(this._ricMsgHandler, this._ledPatternTimeoutMs); } _clearLedPatternRefreshTimer() { if (this._ledPatternRefreshTimer) { clearInterval(this._ledPatternRefreshTimer); this._ledPatternRefreshTimer = null; } } // Mark: Marty system info ------------------------------------------------------------------------------------ /** * Get information Marty system * * @return void * */ async retrieveMartySystemInfo() { // Retrieve system info try { const retrieveResult = await this._ricSystem.retrieveInfo(); return retrieveResult; } catch (err) { RICLog_1.default.error(`retrieveMartySystemInfo: error ${err}`); } return false; } // Mark: RIC Subscription to Updates -------------------------------------------------------------------------------- /** * * subscribeForUpdates * @param enable - true to send command to enable subscription (false to remove sub) * @returns Promise<void> * */ async subscribeForUpdates(enable) { try { const subscribeDisable = '{"cmdName":"subscription","action":"update",' + '"pubRecs":[' + `{"name":"MultiStatus","rateHz":0,}` + '{"name":"PowerStatus","rateHz":0},' + `{"name":"AddOnStatus","rateHz":0}` + ']}'; const subscribeEnable = '{"cmdName":"subscription","action":"update",' + '"pubRecs":[' + `{"name":"MultiStatus","rateHz":${this._subscribeRateHz.toString()}}` + `{"name":"PowerStatus","rateHz":1.0},` + `{"name":"AddOnStatus","rateHz":${this._subscribeRateHz.toString()}}` + ']}'; const ricResp = await this._ricMsgHandler.sendRICRESTCmdFrame(enable ? subscribeEnable : subscribeDisable); // Debug RICLog_1.default.debug(`subscribe enable/disable returned ${JSON.stringify(ricResp)}`); } catch (error) { RICLog_1.default.warn(`getRICCalibInfo Failed subscribe for updates ${error}`); } } async sendFile(fileName, fileContents, progressCallback) { return this._ricFileHandler.fileSend(fileName, RICTypes_1.RICFileSendType.RIC_NORMAL_FILE, fileContents, progressCallback); } // Mark: Streaming -------------------------------------------------------------------------------- streamAudio(streamContents, clearExisting, duration) { if (this._ricStreamHandler && this.isConnected()) { this._ricStreamHandler.streamAudio(streamContents, clearExisting, duration); } } isStreamStarting() { return this._ricStreamHandler.isStreamStarting(); } setLegacySoktoMode(legacyMode) { return this._ricStreamHandler.setLegacySoktoMode(legacyMode); } // Mark: Connection performance-------------------------------------------------------------------------- parkmiller_next(seed) { const hi = Math.round(16807 * (seed & 0xffff)); let lo = Math.round(16807 * (seed >> 16)); lo += (hi & 0x7fff) << 16; lo += hi >> 15; if (lo > 0x7fffffff) lo -= 0x7fffffff; return lo; } async checkConnPerformance() { // Send random blocks of data - these will be ignored by RIC - but will still be counted for performance // evaluation let prbsState = 1; const testData = new Uint8Array(this._testConnPerfBlockSize); for (let i = 0; i < this._testConnPerfNumBlocks; i++) { testData.set([0, (i >> 24) & 0xff, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff, 0x1f, 0x9d, 0xf4, 0x7a, 0xb5]); for (let j = 10; j < this._testConnPerfBlockSize; j++) { prbsState = this.parkmiller_next(prbsState); testData[j] = prbsState & 0xff; } if (this._ricChannel) { await this._ricChannel.sendTxMsg(testData, false); } } // Wait a little to allow RIC to process the data await new Promise(resolve => setTimeout(resolve, this._connPerfRsltDelayMs)); // Get performance const blePerf = await this._ricSystem.getSysModInfoBLEMan(); if (blePerf) { console.log(`startConnPerformanceCheck timer rate = ${blePerf.tBPS}BytesPS`); return blePerf.tBPS; } else { throw new Error('checkConnPerformance: failed to get BLE performance'); } } // Mark: Connection event -------------------------------------------------------------------------- onConnEvent(eventEnum, data = undefined) { // Handle information clearing on disconnect switch (eventEnum) { case RICConnEvents_1.RICConnEvent.CONN_DISCONNECTED_RIC: // Disconnect time this._retryIfLostDisconnectTime = Date.now(); // Check if retry required if (this._retryIfLostIsConnected && this._retryIfLostEnabled) { // Indicate connection disrupted if (this._onEventFn) { this._onEventFn("conn", RICConnEvents_1.RICConnEvent.CONN_ISSUE_DETECTED, RICConnEvents_1.RICConnEventNames[RICConnEvents_1.RICConnEvent.CONN_ISSUE_DETECTED]); } // Retry connection this._retryConnection(); // Don't allow disconnection to propagate until retries have occurred return; } // Invalidate connection details this._ricSystem.invalidate(); break; } // Notify if (this._onEventFn) { this._onEventFn("conn", eventEnum, RICConnEvents_1.RICConnEventNames[eventEnum], data); } } _retryConnection() { // Check timeout if ((this._retryIfLostDisconnectTime !== null) && (Date.now() - this._retryIfLostDisconnectTime < this._retryIfLostForSecs * 1000)) { // Set timer to try to reconnect setTimeout(async () => { // Try to connect const isConn = await this._connectToChannel(); if (!isConn) { this._retryConnection(); } else { // No longer retrying this._retryIfLostDisconnectTime = null; // Indicate connection problem resolved if (this._onEventFn) { this._onEventFn("conn", RICConnEvents_1.RICConnEvent.CONN_ISSUE_RESOLVED, RICConnEvents_1.RICConnEventNames[RICConnEvents_1.RICConnEvent.CONN_ISSUE_RESOLVED]); } } }, this._retryIfLostRetryDelayMs); } else { // No longer connected after retry timeout this._retryIfLostIsConnected = false; // Indicate disconnection if (this._onEventFn) { this._onEventFn("conn", RICConnEvents_1.RICConnEvent.CONN_DISCONNECTED_RIC, RICConnEvents_1.RICConnEventNames[RICConnEvents_1.RICConnEvent.CONN_DISCONNECTED_RIC]); } // Invalidate connection details this._ricSystem.invalidate(); } } async _connectToChannel() { // Connect try { if (this._ricChannel) { const connected = await this._ricChannel.connect(this._channelConnLocator); if (connected) { this._retryIfLostIsConnected = true; return true; } } } catch (error) { RICLog_1.default.error(`RICConnector.connect() error: ${error}`); } return false; } // Mark: OTA Update ----------------------------------------------------------------------------------------- _onUpdateEvent(eventEnum, data = undefined) { // Notify if (this._onEventFn) { this._onEventFn("ota", eventEnum, RICUpdateEvents_1.RICUpdateEventNames[eventEnum], data); } } async otaUpdateCheck() { if (!this._ricUpdateManager) return RICUpdateEvents_1.RICUpdateEvent.UPDATE_NOT_CONFIGURED; return await this._ricUpdateManager.checkForUpdate(this._ricSystem.getCachedSystemInfo()); } async otaUpdateStart() { if (!this._ricUpdateManager) return RICUpdateEvents_1.RICUpdateEvent.UPDATE_NOT_CONFIGURED; return await this._ricUpdateManager.firmwareUpdate(); } async otaUpdateCancel() { if (!this._ricUpdateManager) return; return await this._ricUpdateManager.firmwareUpdateCancel(); } // Mark: Set AddOn config ----------------------------------------------------------- /** * * setAddOnConfig - set a specified add-on's configuration * @param serialNo used to identify the add-on * @param newName name to refer to add-on by * @returns Promise<RICOKFail> * */ async setAddOnConfig(serialNo, newName) { try { const msgRslt = await this._ricMsgHandler.sendRICRESTURL(`addon/set?SN=${serialNo}&name=${newName}`); return msgRslt; } catch (error) { return new RICTypes_1.RICOKFail(); } } /** * deleteAddOn - remove an addon from the addonlist on RIC * @param serialNo used to identify the add-on * @returns Promise<RICOKFail> */ async deleteAddOn(serialNo) { try { const msgRslt = await this._ricMsgHandler.sendRICRESTURL(`addon/del?SN=${serialNo}`); return msgRslt; } catch (error) { return new RICTypes_1.RICOKFail(); } } // Mark: Identify AddOn ----------------------------------------------------------- /** * * identifyAddOn - send the 'identify' command to a specified add-on * @param name used to identify the add-on * @returns Promise<RICOKFail> * */ async identifyAddOn(name) { try { const msgRslt = await this._ricMsgHandler.sendRICRESTURL(`elem/${name}/json?cmd=raw&hexWr=F8`); return msgRslt; } catch (error) { return new RICTypes_1.RICOKFail(); } } } exports.default = RICConnector; //# sourceMappingURL=RICConnector.js.map