UNPKG

@robotical/ricjs

Version:

Javascript/TS library for Robotical RIC

761 lines (685 loc) 27.2 kB
///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // RICJS // Communications Library // // Rob Dobson & Chris Greening 2020-2022 // (C) 2020-2022 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// import { RICFileDownloadFn, RICFileSendType, RICFWInfo, RICHWFWUpdRslt, RICOKFail, RICSystemInfo, RICUpdateInfo, } from "./RICTypes"; import { RICUpdateEvent, RICUpdateEventFn } from "./RICUpdateEvents"; import RICMsgHandler from "./RICMsgHandler"; import axios from "axios"; import RICFileHandler from "./RICFileHandler"; import RICLog from "./RICLog"; import RICSystem from "./RICSystem"; import RICUtils from "./RICUtils"; import RICChannel from "./RICChannel"; export default class RICUpdateManager { // Version info private _latestVersionInfo: RICUpdateInfo | null = null; private _updateESPRequired = false; private _updateElemsRequired = false; // FW update private readonly FW_UPDATE_CHECKS_BEFORE_ASSUME_FAILED = 10; private readonly ELEM_FW_CHECK_LOOPS = 36; // Progress levels private _progressAfterDownload = 0.1; private _progressDuringUpload = 0.8; private _progressDuringRestart = 0.015; // there may be two restarts during an update private _progressAfterUpload = this._progressAfterDownload + this._progressDuringUpload + 2 * this._progressDuringRestart; private _idToConnectTo: string | null = null; private _nameToConnectTo: string | null = null; private _ricHwRevNo: number | null = null; // TESTS - set to true for testing OTA updates ONLY private readonly TEST_TRUNCATE_ESP_FILE = false; private readonly TEST_PRETEND_ELEM_UPDATE_REQD = false; public TEST_PRETEND_INITIAL_VERSIONS_DIFFER = false; // this is public so it can be set from the front-end to force an update private readonly TEST_PRETEND_FINAL_VERSIONS_MATCH = false; private readonly TEST_SKIP_FW_UPDATE = false; constructor( private _ricMsgHandler: RICMsgHandler, private _ricFileHandler: RICFileHandler, private _ricSystem: RICSystem, private _eventListener: RICUpdateEventFn, private _firmwareTypeStrForMainFw: string, private _currentAppVersion: string, private _fileDownloader: RICFileDownloadFn, private _firmwareUpdateURL: string, private _firmwareBaseURL: string, private _ricChannel: RICChannel | null ) {} async checkForUpdate( systemInfo: RICSystemInfo | null ): Promise<RICUpdateEvent> { if (systemInfo === null) { return RICUpdateEvent.UPDATE_NOT_AVAILABLE; } this._latestVersionInfo = null; try { // handle url modifications let updateURL = this._firmwareUpdateURL; const ricSystemInfo = this._ricSystem.getCachedSystemInfo(); if (!ricSystemInfo || !ricSystemInfo.RicHwRevNo){ RICLog.debug("checkForUpdate failed to get RIC info, either no channel or no system info"); RICLog.debug("ricSystemInfo:" + JSON.stringify(ricSystemInfo)); RICLog.debug("ricHwRevNo:" + ricSystemInfo!.RicHwRevNo); return RICUpdateEvent.UPDATE_FAILED; } updateURL = updateURL.replace( "{HWRevNo}", ricSystemInfo.RicHwRevNo.toString() ); // debug RICLog.debug(`Update URL: ${updateURL}`); const response = await axios.get(updateURL); this._latestVersionInfo = response.data; } catch (error) { RICLog.debug("checkForUpdate failed to get latest from internet"); } if (this._latestVersionInfo === null) { return RICUpdateEvent.UPDATE_CANT_REACH_SERVER; } // Check the version and incomplete previous hw-elem update if needed try { const updateRequired = await this._isUpdateRequired( this._latestVersionInfo, systemInfo ); RICLog.debug( `checkForUpdate systemVersion ${systemInfo?.SystemVersion} available online ${this._latestVersionInfo?.firmwareVersion} updateRequired ${updateRequired}` ); if (updateRequired) { if ( RICUtils.isVersionGreater( this._latestVersionInfo.minimumUpdaterVersion.ota, this._currentAppVersion ) ) { RICLog.debug( `App version ${this._currentAppVersion} but version ${this._latestVersionInfo.minimumUpdaterVersion.ota} required` ); return RICUpdateEvent.UPDATE_APP_UPDATE_REQUIRED; } else { return RICUpdateEvent.UPDATE_IS_AVAILABLE; } } else { return RICUpdateEvent.UPDATE_NOT_AVAILABLE; } } catch (error) { RICLog.debug("Failed to get latest version from internet"); } return RICUpdateEvent.UPDATE_CANT_REACH_SERVER; } async _isUpdateRequired( latestVersion: RICUpdateInfo, systemInfo: RICSystemInfo ): Promise<boolean> { this._updateESPRequired = false; this._updateElemsRequired = false; // Perform the version check this._updateESPRequired = RICUtils.isVersionGreater( latestVersion.firmwareVersion, systemInfo.SystemVersion ); // Test ONLY pretend an update is needed if (this.TEST_PRETEND_INITIAL_VERSIONS_DIFFER) { this._updateESPRequired = true; } // TODO: check if elem updates are required using elemsUpdatesRequired() // Check if a previous hw-elem update didn't complete - but no point if we would update anyway if (!this._updateESPRequired) { try { const elUpdRslt = await this._ricMsgHandler.sendRICRESTURL<RICHWFWUpdRslt>("hwfwupd"); // Check result this._updateElemsRequired = elUpdRslt.rslt === "ok" && elUpdRslt.st.i === 1; // Debug if (this._updateElemsRequired) { RICLog.debug("isUpdateRequired - prev incomplete"); } else { RICLog.debug("isUpdateRequired - prev complete"); } // Test ONLY pretend an element update is needed if (this.TEST_PRETEND_ELEM_UPDATE_REQD) { this._updateElemsRequired = true; } } catch (error) { RICLog.debug( "isUpdateRequired failed to get hw-elem firmware update status" ); } } else { this._updateElemsRequired = true; } return this._updateESPRequired || this._updateElemsRequired; } elemUpdateRequired(expectedVersion: string, actualVersion: string, dtid: number, addr: number, elemType: string){ if (elemType != "SmartServo" && elemType != "RSAddOn") return false; const outdated = RICUtils.isVersionGreater(expectedVersion, actualVersion); if (!outdated) return false; // if stm32, we only want to update the base elems const stm32_dtid_mask = 0xFFFFFF00 const stm32_dtid_id = 0x00000100 const stm32_base_elems = [0x10, 0x13, 0x16]; if ((dtid & stm32_dtid_mask) == stm32_dtid_id){ return stm32_base_elems.includes(addr); } return true; } getExpectedVersion(firmwareVersions: any, dtid: number){ if (Object.prototype.hasOwnProperty.call(firmwareVersions["dtid"], dtid)){ return firmwareVersions["dtid"][dtid]["version"]; } return null; } async elemUpdatesRequired(): Promise<Array<any> | null> { const elemsToUpdate = []; const firmwareVersionsUrl = `${this._firmwareBaseURL}/firmware/firmwareVersions.json`; // get elem firmware expected versions const firmwareVersionResponse = await fetch(firmwareVersionsUrl); if (!firmwareVersionResponse.ok) return null; const firmwareVersionsJson = await firmwareVersionResponse.json(); RICLog.debug(`firmwareVersions response ${JSON.stringify(firmwareVersionsJson)}`); // get connected elements const hwstatus: any = await this._ricMsgHandler.sendRICRESTURL("hwstatus"); RICLog.debug(`hwstatus response: ${JSON.stringify(hwstatus)}`); const hwElems = hwstatus["hw"]; // TODO: check if hwstatus is reporting versions as "0.0.0", if so pause and retry as robot is probably still starting up for (const elem in hwElems){ // TODO: use RICHWElem type const dtid = parseInt(hwElems[elem]["whoAmITypeCode"], 16); const expectedVersion = this.getExpectedVersion(firmwareVersionsJson, dtid); const actualVersion = hwElems[elem]["versionStr"]; const addr = parseInt(hwElems[elem]["addr"], 16); const elemType = hwElems[elem]["type"]; const elemName = hwElems[elem]["name"]; RICLog.debug(`hwElem ${elemName} dtid ${dtid} addr ${addr} type ${elemType} expectedVersion ${expectedVersion} actual version ${actualVersion}`); if (expectedVersion){ hwElems[elem]["expectedVersion"] = expectedVersion; if (this.elemUpdateRequired(expectedVersion, actualVersion, dtid, addr, elemType)) elemsToUpdate.push(hwElems[elem]); } } return elemsToUpdate; } // Mark: Firmware udpate ------------------------------------------------------------------------------------------------ async firmwareUpdate(): Promise<RICUpdateEvent> { // Check valid if (this._latestVersionInfo === null) return RICUpdateEvent.UPDATE_NOT_CONFIGURED; // save RIC info for later restarts const ricSystemInfo = this._ricSystem.getCachedSystemInfo(); if (this._ricChannel && ricSystemInfo !== null) { const deviceInfo: BluetoothDevice = this._ricChannel.getConnectedLocator() as BluetoothDevice; this._idToConnectTo = deviceInfo.id; this._nameToConnectTo = deviceInfo.name || "Marty"; this._ricHwRevNo = ricSystemInfo.RicHwRevNo; RICLog.debug("iDToConnectTo " + this._idToConnectTo); RICLog.debug("nameToConnectTo " + this._nameToConnectTo); RICLog.debug("RIC HW Rev " + this._ricHwRevNo.toString()); } else { RICLog.debug( "firmwareUpdate failed to get RIC info, either no channel or no system info" ); return RICUpdateEvent.UPDATE_FAILED; } // Update started this._eventListener(RICUpdateEvent.UPDATE_STARTED); this._eventListener(RICUpdateEvent.UPDATE_PROGRESS, { stage: "Downloading firmware", progress: 0, updatingFilesystem: false, }); // parse version file to extract only "ota" files const firmwareList: Array<RICFWInfo> = []; let mainFwInfo: RICFWInfo | null = null; this._latestVersionInfo.files.forEach((fileInfo) => { if (fileInfo.updaters.includes("ota")) { fileInfo.downloadUrl = fileInfo.firmware || fileInfo.downloadUrl; if (fileInfo.elemType === this._firmwareTypeStrForMainFw) { mainFwInfo = fileInfo; } else { firmwareList.push(fileInfo); } RICLog.debug( `fwUpdate selected file ${fileInfo.destname} for download` ); } }); // Add the main firware if it is required if (this._updateESPRequired && mainFwInfo != null) { firmwareList.unshift(mainFwInfo); // add to front of array so it's downloaded first } // Binary data downloaded from the internet const firmwareData = new Array<Uint8Array>(); // Iterate through the firmware entities const numFw = firmwareList.length; try { for (let fwIdx = 0; fwIdx < firmwareList.length; fwIdx++) { // Download the firmware RICLog.debug( `fwUpdate downloading file URI ${firmwareList[fwIdx].downloadUrl}` ); const downloadResult = await this._fileDownloader( firmwareList[fwIdx].downloadUrl, (received: number, total: number) => { const currentProgress = ((fwIdx + received / total) / numFw) * this._progressAfterDownload; this._eventListener(RICUpdateEvent.UPDATE_PROGRESS, { stage: "Downloading firmware", progress: currentProgress, updatingFilesystem: false, }); } ); if (downloadResult.downloadedOk && downloadResult.fileData != null) { firmwareData.push(downloadResult.fileData); } else { this._eventListener(RICUpdateEvent.UPDATE_FAILED); throw Error("file download res null"); } } } catch (error: unknown) { RICLog.debug(`fwUpdate error ${error}`); this._eventListener(RICUpdateEvent.UPDATE_FAILED); return RICUpdateEvent.UPDATE_FAILED; } // Test ONLY truncate the main firmware if ( this._updateESPRequired && mainFwInfo != null && this.TEST_TRUNCATE_ESP_FILE ) { firmwareData[0] = new Uint8Array(500); } // Calculate total length of data let totalBytes = 0; for (const fileData of firmwareData) { totalBytes += fileData.length; } // Debug RICLog.debug( `fwUpdate got ok ${firmwareData.length} files total ${totalBytes} bytes` ); // Start uploading this._eventListener(RICUpdateEvent.UPDATE_PROGRESS, { stage: "Starting firmware upload", progress: this._progressAfterDownload, updatingFilesystem: false, }); // Upload each file let updateStage = "Uploading new firmware\nThis may take a while, please be patient"; let updatingFilesystem = false; try { let sentBytes = 0; for (let fwIdx = 0; fwIdx < firmwareData.length; fwIdx++) { RICLog.debug( `fwUpdate uploading file name ${firmwareList[fwIdx].destname} len ${firmwareData[fwIdx].length}` ); const elemType = firmwareList[fwIdx].elemType === this._firmwareTypeStrForMainFw ? RICFileSendType.RIC_FIRMWARE_UPDATE : RICFileSendType.RIC_NORMAL_FILE; let percComplete = (sentBytes / totalBytes) * this._progressDuringUpload + this._progressAfterDownload; if ( !updatingFilesystem && elemType == RICFileSendType.RIC_NORMAL_FILE ) { // start of filesystem updates updateStage = "Updating system files\nThis may take a while, please be patient\nUpdate cannot be cancelled during this stage\n"; updatingFilesystem = true; // emit event so app can deactivate cancel button this._eventListener(RICUpdateEvent.UPDATE_PROGRESS, { stage: updateStage, progress: percComplete, updatingFilesystem: updatingFilesystem, }); if (this._ricHwRevNo == 1) { // the spiffs filesystem used on rev 1 doesn't delete files properly and has issues being more than 75% full // it must be formatted to prevent issues after multiple OTA updates // Reformat filesystem. This will take a few seconds so set a long timeout for the response RICLog.debug(`Beginning file system update. Reformatting FS.`); await this._ricMsgHandler.sendRICRESTURL<RICOKFail>( "reformatfs", 15000 ); // trigger and wait for reboot RICLog.debug(`Restarting RIC`); await this._ricSystem.runCommand("reset", {}); if (!(await this.waitForRestart(percComplete))) { this._eventListener(RICUpdateEvent.UPDATE_FAILED); return RICUpdateEvent.UPDATE_FAILED; } } } if ( elemType == RICFileSendType.RIC_FIRMWARE_UPDATE && this.TEST_SKIP_FW_UPDATE ) { RICLog.debug("fwUpdate: Skipping FW update"); } else { await this.fileSend( firmwareList[fwIdx].destname, elemType, firmwareData[fwIdx], (_, __, progress) => { let percComplete = ((sentBytes + progress * firmwareData[fwIdx].length) / totalBytes) * this._progressDuringUpload + this._progressAfterDownload; if (elemType == RICFileSendType.RIC_NORMAL_FILE) percComplete += this._progressDuringRestart * 2; if (percComplete > 1.0) percComplete = 1.0; RICLog.debug( `fwUpdate progress ${progress.toFixed( 2 )} sent ${sentBytes} len ${ firmwareData[fwIdx].length } total ${totalBytes} propComplete ${percComplete.toFixed(2)}` ); this._eventListener(RICUpdateEvent.UPDATE_PROGRESS, { stage: updateStage, progress: percComplete, updatingFilesystem: updatingFilesystem, }); } ); } sentBytes += firmwareData[fwIdx].length; if (elemType == RICFileSendType.RIC_FIRMWARE_UPDATE) { percComplete = (sentBytes / totalBytes) * this._progressDuringUpload + this._progressAfterDownload; // if the element was firmware, RIC will now restart automatically if ( !(await this.waitForRestart( percComplete, this._latestVersionInfo?.firmwareVersion )) ) { this._eventListener(RICUpdateEvent.UPDATE_FAILED); return RICUpdateEvent.UPDATE_FAILED; } } } } catch (error) { RICLog.debug(`fwUpdate error ${error}`); this._eventListener(RICUpdateEvent.UPDATE_FAILED); return RICUpdateEvent.UPDATE_FAILED; } // TODO: check this is working const allElemsUpdatedOk = await this.updateElems(); /* // Issue requests for hw-elem firmware updates let elemFwIdx = 0; let allElemsUpdatedOk = true; for (const elemFw of firmwareList) { // Update progress const percComplete = this._progressAfterUpload + ((1 - this._progressAfterUpload) * elemFwIdx) / firmwareList.length; this._eventListener(RICUpdateEvent.UPDATE_PROGRESS, { stage: "Updating elements", progress: percComplete, updatingFilesystem: true, }); elemFwIdx++; // Check element is not main if (elemFw.elemType === this._firmwareTypeStrForMainFw) continue; // Non-firmware elemTypes if (this._nonFirmwareElemTypes.includes(elemFw.elemType)) continue; await this.updateElem(elemFw); } */ // Done update this._eventListener(RICUpdateEvent.UPDATE_PROGRESS, { stage: "Finished", progress: 1, updatingFilesystem: false, }); let updateResult = RICUpdateEvent.UPDATE_SUCCESS_ALL; if (allElemsUpdatedOk) { this._eventListener(updateResult, this._ricSystem.getCachedSystemInfo()); } else { updateResult = RICUpdateEvent.UPDATE_SUCCESS_MAIN_ONLY; this._eventListener(updateResult, this._ricSystem.getCachedSystemInfo()); } return updateResult; } async updateElems(elemsToUpdate: Array<any> | null = null): Promise<boolean>{ if (elemsToUpdate === null) elemsToUpdate = await this.elemUpdatesRequired(); if (elemsToUpdate === null) return false; let progress = this._progressAfterUpload; const progressPerElem = (1 - progress) / elemsToUpdate.length; let allElemsUpdatedOk = true; const updatedDtids: Array<number> = []; for (const elem in elemsToUpdate){ const dtid = parseInt(elemsToUpdate[elem]["whoAmITypeCode"], 16); const expectedVersion = elemsToUpdate[elem]["expectedVersion"]; const actualVersion = elemsToUpdate[elem]["versionStr"]; const elemType = elemsToUpdate[elem]["type"]; const elemName = elemsToUpdate[elem]["name"]; RICLog.debug(`hwElem ${elemsToUpdate[elem]["name"]} dtid ${dtid} type ${elemType} expectedVersion ${expectedVersion} actual version ${actualVersion}`); if (expectedVersion){ // only need to send each firmware file once const sendFile = updatedDtids.includes(dtid) ? false : true; if (!await this.updateHWElem(elemName, dtid, elemType, expectedVersion, sendFile)) allElemsUpdatedOk = false; updatedDtids.push(dtid); progress += progressPerElem; this._eventListener(RICUpdateEvent.UPDATE_PROGRESS, { stage: "Updating elements", progress: progress, updatingFilesystem: true, }); } } return allElemsUpdatedOk; } async updateHWElem(elemName: string, dtid: number, elemType: string, expectedVersion: string, sendFile: boolean){ const dtidStr = dtid.toString(16).padStart(8, "0"); const destFwFilename = `fw${dtidStr}.rfw`; if (sendFile){ const firmwareUrl = `${this._firmwareBaseURL}/firmware/${dtidStr}/fw${dtidStr}-${expectedVersion}.rfw`; const firmware = await this._fileDownloader(firmwareUrl, (received, total)=>{RICLog.debug(`download received ${received} of total ${total}`)}); if (!firmware.downloadedOk || !firmware.fileData) return false; if (!await this.fileSend(destFwFilename, RICFileSendType.RIC_NORMAL_FILE, firmware.fileData, (sent, total, progress)=>{console.log(`sent ${sent} total ${total} progress ${progress}`)})) return false; } // double check file on RIC has the correct version const fwResp = await this._ricMsgHandler.sendRICRESTURL<RICHWFWUpdRslt>(`hwfwupd//${destFwFilename}`); if (fwResp.st.v != expectedVersion) return false; const fwInfo: RICFWInfo = { elemType: elemType, destname: destFwFilename, version: "", md5: "", releaseNotes: "", comments: "", updaters: [], downloadUrl: "" }; return await this.updateElem(fwInfo, elemName); } async updateElem(elemFw: RICFWInfo, elemNameOrAll = "all") { // Start hw-elem update const updateCmd = `hwfwupd/${elemFw.elemType}/${elemFw.destname}/${elemNameOrAll}`; try { await this._ricMsgHandler.sendRICRESTURL<RICOKFail>(updateCmd); } catch (error) { RICLog.debug( `fwUpdate failed to start hw-elem firmware update cmd ${updateCmd}` ); return false; } let allElemsUpdatedOk = false; // Check the status for ( let updateCheckLoop = 0; updateCheckLoop < this.ELEM_FW_CHECK_LOOPS; updateCheckLoop++ ) { try { // Wait for process to start on ESP32 await new Promise((resolve) => setTimeout(resolve, 5000)); // Get result (or status) const elUpdRslt = await this._ricMsgHandler.sendRICRESTURL<RICHWFWUpdRslt>("hwfwupd"); // Check result if ( elUpdRslt.rslt === "ok" && (elUpdRslt.st.s === "idle" || elUpdRslt.st.s === "done") ) { RICLog.debug( `fwUpdate hw-elem firmware updated ok - status ${elUpdRslt.st.s} rsltmsg ${elUpdRslt.st.m}` ); // Check if any update outstanding (incomplete === 0) allElemsUpdatedOk = elUpdRslt.st.i === 0; break; } } catch (error) { RICLog.debug(`failed to get hw-elem firmware update status`); } } return allElemsUpdatedOk; } async manualReconnect() { return this._ricChannel?.connect({ name: this._nameToConnectTo, localName: this._nameToConnectTo, id: this._idToConnectTo || "", rssi: 0, }); } async waitForRestart( percComplete: number, checkFwVersion: string | null = null ) { RICLog.debug( `fwUpdate: Waiting for restart. percComplete ${percComplete}, checkFwVersion: ${checkFwVersion}` ); // sending the appropriate disconnect event to the UI so it knows that the device is disconnected this._eventListener(RICUpdateEvent.UPDATE_RIC_DISCONNECTED); // Wait for firmware update to complete, restart to occur // and BLE reconnection to happen const waitTime = 5000; const iterations = 3; for (let i = 0; i < iterations; i++) { await new Promise((resolve) => setTimeout(resolve, waitTime)); this._eventListener(RICUpdateEvent.UPDATE_PROGRESS, { stage: "Restarting Marty", progress: percComplete + (this._progressDuringRestart * i) / 3, updatingFilesystem: true, }); RICLog.debug( "fwUpdate waiting for reset, seconds: " + i * waitTime + " / " + iterations * waitTime ); } // Attempt to get status from main ESP32 update // The ESP32 will power cycle at this point so we need to wait a while let versionConfirmed = false; for ( let fwUpdateCheckCount = 0; fwUpdateCheckCount < this.FW_UPDATE_CHECKS_BEFORE_ASSUME_FAILED; fwUpdateCheckCount++ ) { try { // Get version RICLog.debug( `fwUpdate attempting to get RIC version attempt ${fwUpdateCheckCount}` ); const systemInfo = await this._ricSystem.getRICSystemInfo(true); RICLog.debug( `fwUpdate version rslt "${systemInfo.rslt}" RIC Version ${systemInfo.SystemVersion}` ); if (systemInfo.rslt !== "ok") { let shouldContiue = true; // if this is not the last attempt, or if the manual reconnect fails, we should continue the loop if (fwUpdateCheckCount === this.FW_UPDATE_CHECKS_BEFORE_ASSUME_FAILED - 1) { // we have failed to get the version after the last attempt, so we need to fallback to manually reconnecting const didConnect = await this.manualReconnect(); if (didConnect) shouldContiue = false; } if (shouldContiue) continue; } // at this point we are connected to BLE again, so we can send to the UI the appropriate events this._eventListener(RICUpdateEvent.UPDATE_RIC_RECONNECTED); if (checkFwVersion != null) { // Check version versionConfirmed = RICUtils.isVersionEqual( checkFwVersion, systemInfo.SystemVersion ); RICLog.debug(`fwUpdate got version rslt ${versionConfirmed}`); } else { versionConfirmed = true; } // Test fiddle to say it worked! if (this.TEST_PRETEND_FINAL_VERSIONS_MATCH) { versionConfirmed = true; } break; } catch (error) { RICLog.debug( `fwUpdate failed to get version attempt', ${fwUpdateCheckCount} error ${error}` ); } } return versionConfirmed; } async firmwareUpdateCancel() { this._eventListener(RICUpdateEvent.UPDATE_CANCELLING); await this.fileSendCancel(); } // Mark: File Transfer ------------------------------------------------------------------------------------ /** * * fileSend - start file transfer * @param fileName name of file to send * @param fileType normal file or firmware * @param fileContents contenst of the file (binary object) * @returns Promise<boolean> * */ async fileSend( fileName: string, fileType: RICFileSendType, fileContents: Uint8Array, progressCallback: (sent: number, total: number, progress: number) => void ): Promise<boolean> { return await this._ricFileHandler.fileSend( fileName, fileType, fileContents, progressCallback ); } fileSendCancel() { return this._ricFileHandler.fileSendCancel(); } }