ecosystem-rapid.js

import API from './ecosystem-base.js';
import {Logger} from './../function/log-helper.js';
import {ErrorCode, ExceptionIdMap} from './../exception/exceptionDesc.js';
import {InfoType, WarningType} from '../information/informationCode.js';
import {appendDataToErrInstance} from '../utils/utils.js';

export const factoryApiRapid = function (r) {
  const logModule = 'ecosystem-rapid';

  /**
   * The API.RAPID namespace provides a comprehensive set of interfaces for managing and interacting with RAPID programs on the controller.
   * It includes methods for controlling program execution, managing tasks, modules, and variables, as well as monitoring execution states and subscribing to variable changes.
   * This class simplifies the development and integration of RAPID-based automation solutions, enabling developers to efficiently interact with and control RAPID programs.
   * @alias API.RAPID
   * @namespace
   */
  r.RAPID = new (function () {
    this.variables = [];
    this.subscriptions = new Map();
    this.variableInstanceMap = new Map();

    /**
     * @alias MODULETYPE
     * @memberof API.RAPID
     * @readonly
     * @property {enum} Program Program module
     * @property {enum} System System module
     * @enum {string}
     */
    const MODULETYPE = {
      Program: 'program',
      System: 'system',
    };
    this.MODULETYPE = MODULETYPE;

    const EXECUTIONSTATE = {
      Running: 'running',
      Stopped: 'stopped',
    };
    this.EXECUTIONSTATE = EXECUTIONSTATE;

    /**
     * @memberof API
     * @param {RWS.Rapid.MonitorResources} res
     * @param {Function} func
     * @param {string} [task]
     * @returns {Promise<any>}
     * @private
     */
    const subscribeRes = async function (res, func, task = 'T_ROB1') {
      try {
        if (API.isSubscriptionBlocked) {
          Logger.w(WarningType.RWSSubscriptionBlocked, 'API.RAPID: Subscription ON rapid resource is disabled');
          return;
        }

        const monitor = RWS.Rapid.getMonitor(res, task);
        monitor.addCallbackOnChanged(func);
        await monitor.subscribe();
      } catch (e) {
        return API.rejectWithStatus(`Failed to subscribe to ${res}`, e, {
          errorCode: ErrorCode.RWSSubscriptionFailed,
          msgParams: {resourceName: RWS.Rapid.MonitorResources.execution},
        });
      }
    };

    /**
     * @typedef searchSymbolProps
     * @prop {string} [task] The task where the searching runs. (default: 'T_ROB1')
     * @prop {string} [module] The module where the searching runs.
     * @prop {boolean} [isInUse] Only return symbols that are used in a RAPID program,
     * i.e. a type declaration that has no declared variable will not be returned when this flag is set true.
     * @prop {object} [dataType] Type of the data, e.g. 'num'(only one data type is supported)
     * @prop {number} [symbolType] Can be the following values:
     *       <br>&emsp;undefined: 0
     *       <br>&emsp;constant: 1
     *       <br>&emsp;variable: 2
     *       <br>&emsp;persistent: 4
     *       <br>&emsp;function: 8
     *       <br>&emsp;procedure: 16
     *       <br>&emsp;trap: 32
     *       <br>&emsp;module: 64
     *       <br>&emsp;task: 128
     *       <br>&emsp;routine: 8 + 16 + 32
     *       <br>&emsp;rapidData: 1 + 2 + 4
     *       <br>&emsp;any: 255
     * @prop {string | Array} [name] Name of the data symbol (not case-sensitive)
     * @memberof API.RAPID
     */

    /**
     * @alias searchSymbol
     * @memberof API.RAPID
     * @param {searchSymbolProps} props
     * @returns {Promise<RWS.Rapid.SymbolSearchResult[]>}
     * A Promise with list of objects. Each object contains:
     *      <br>&emsp;(string) name name of the data symbol
     *      <br>&emsp;([string]) scope symbol scope
     *      <br>&emsp;(string) symbolType type of the symbol, e.g. 'pers'
     *      <br>&emsp;(string) dataType type of the data, e.g. 'num'
     * @private
     * @todo not yet working properly
     */
    const searchSymbol = async function ({
      task = 'T_ROB1',
      module = null,
      isInUse = false,
      dataType = '',
      symbolType = RWS.Rapid.SymbolTypes.rapidData,
      name = '',
    } = {}) {
      let elements = [];
      try {
        var properties = RWS.Rapid.getDefaultSearchProperties();
        let url = `RAPID/${task}`;
        if (module) url = url + `/${module}`;
        properties.searchURL = url;
        properties.types = symbolType;
        properties.isInUse = isInUse;
        var hits = [];
        if (name instanceof Array) {
          for (const index in name) {
            const regexp = name[index] !== '' ? `^.*${name[index]}.*$` : '';
            hits.push(...(await RWS.Rapid.searchSymbols(properties, dataType, regexp)));
          }
        } else {
          const regexp = name !== '' ? `^.*${name}.*$` : '';
          hits = await RWS.Rapid.searchSymbols(properties, dataType, regexp);
        }

        if (hits.length > 0) {
          for (let i = 0; i < hits.length; i++) {
            elements.push(hits[i]);
          }
        }
        return elements;
      } catch (e) {
        return API.rejectWithStatus(`Failed to search symbol ${name} -  module: ${module}`, e, {
          errorCode: ErrorCode.FailedToSearchSymbol,
        });
      }
    };

    /**
     * Gets available value-type RAPID RECORDs.
     * @alias getValRecordTypes
     * @memberof API.RAPID
     * @param {string} [task] The task name.
     * @returns {Promise<object>}
     * @example
     * await API.RAPID.getVariableTypes('T_ROB1')
     */
    this.getValRecordTypes = async function (task = 'T_ROB1') {
      try {
        let types = [];
        let searchFlag = false;
        let exturl = 'symbols/search';
        while (!searchFlag) {
          let searchedResult = await API.RWS.RAPID.getRecordTypes(task, exturl);
          let resources = searchedResult['_embedded']['resources'];
          for (let i = 0; i < resources.length; i++) {
            if (resources[i].valtyp == 'val') {
              types.push(resources[i].name);
            }
          }
          if (typeof searchedResult._links.next == 'undefined') {
            searchFlag = true;
          } else {
            exturl = searchedResult._links.next.href;
          }
        }
        return types;
      } catch (e) {
        return API.rejectWithStatus(`Failed to get RAPID value-type RECORD types in task ${task}`, e, {
          errorCode: ErrorCode.FailedToGetRecordDataStructure,
        });
      }
    };

    /**
     * Searchs for available tasks.
     * @alias searchTasks
     * @memberof API.RAPID
     * @param {string} [filter] Only symbols in the string pattern.
     * @param {boolean} [caseSensitive] If the value is set to the default "false", non-case-sensitive is applied for the filter; otherwise, case-sensitive is applied.
     * @returns {Promise<object>}
     * @example
     * await API.RAPID.searchTasks()
     */
    this.searchTasks = async function (filter = '', caseSensitive = false) {
      if (typeof filter !== 'string') {
        throw new Error('The filter string should be a valid string.');
      }
      try {
        const allTasks = await RWS.Rapid.getTasks();
        const taskNames = allTasks.map((t) => t.getName());
        const flags = caseSensitive ? '' : 'i';
        const regex = new RegExp(filter, flags);
        const filteredTaskNames = taskNames.filter((element) => regex.test(element));

        return taskNames.filter((element) => regex.test(element));
      } catch (error) {
        return API.rejectWithStatus('Failed to get tasks', error, {
          errorCode: ErrorCode.FailedToSearchTasks,
        });
      }
    };

    /**
     * Monitors the state change of the RAPID program execution. It is possible to call a callback function every time the state is changed.
     * Current state is stored in {@link executionState} variable. Additionally, {@link isRunning}
     * is updated correspondingly.
     * @alias monitorExecutionState
     * @memberof API.RAPID
     * @param {function} [callback] The callback function that is called when the execution state changes.
     * @example
     * // The callback will be executed when execution status is changed
     * const execStatus = (value)=>{console.log("Execution status is:",value);}
     * await API.RAPID.monitorExecutionState(execStatus);
     */
    this.monitorExecutionState = async function (callback = null) {
      if (this.executionState === undefined) {
        this.executionState = await RWS.Rapid.getExecutionState();
        this.isRunning = this.executionState === RWS.Rapid.ExecutionStates.running ? true : false;
        const cbExecState = function (data) {
          this.executionState = data;
          data === RWS.Rapid.ExecutionStates.running ? (this.isRunning = true) : (this.isRunning = false);
          API._events.trigger('execution', data);
          Logger.i(InfoType.RobotOperation, `Controller Execution status is changed to: ${this.executionState}`);
        };
        await subscribeRes(RWS.Rapid.MonitorResources.execution, cbExecState.bind(this));
        callback(this.executionState);
      }
      API._events.on('execution', callback);
    };

    /**
     * Sets program pointer to main
     * @note SPOC systems (e.g., RobotWare 8): Write access must be held before setting program pointer. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
     * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as edit mastership is handled internally.
     * @alias setPPToMain
     * @memberof API.RAPID
     * @example
     * await API.RAPID.setPPToMain()
     */
    this.setPPToMain = async function () {
      try {
        await API.RWS.requestMastership('edit');
        try {
          await RWS.Rapid.resetPP();
        } catch (error) {
          return API.rejectWithStatus('Failed to reset RAPID program pointer', error, {
            errorCode: ErrorCode.FailedToResetPP,
          });
        }
      } finally {
        await API.RWS.releaseMastership('edit');
      }
    };

    /**
     * Sets program pointer to routine
     * @note SPOC systems (e.g., RobotWare 8): Write access must be held before setting program pointer. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
     * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as edit mastership is handled internally.
     * @alias setPPToRoutine
     * @memberof API.RAPID
     * @example
     * await API.RAPID.setPPToRoutine("MainModule","proc1")
     */
    this.setPPToRoutine = async function (moduleName, routineName, taskName = 'T_ROB1') {
      if (!(moduleName && routineName)) return;

      try {
        await API.RWS.requestMastership('edit');
        try {
          await API.RWS.RAPID.setPPToRoutine(moduleName, routineName, taskName);
        } catch (error) {
          return API.rejectWithStatus('Failed to set program pointer to routine', error, {
            errorCode: ErrorCode.FailedToSetPPToRoutine,
          });
        }
      } finally {
        await API.RWS.releaseMastership('edit');
      }
    };

    /**
     * Gets the program pointer
     * @alias getProgramPointer
     * @memberof API.RAPID
     * @returns {Promise<objecy>} A promise with an object containing the program pointer information.
     * @example
     * await API.RAPID.getProgramPointer()
     */
    this.getProgramPointer = async function (taskName = 'T_ROB1') {
      try {
        let pcpInfo = await API.RWS.RAPID.getPointersInfo(taskName);
        let programPointer = pcpInfo[0];
        let beginPos =
          programPointer['beginposition'] != undefined
            ? programPointer['beginposition'].split(',')
            : [undefined, undefined];
        let endPos =
          programPointer['endposition'] != undefined
            ? programPointer['endposition'].split(',')
            : [undefined, undefined];
        let moduleName =
          programPointer['modulemame'] != undefined ? programPointer['modulemame'] : programPointer['modulename'];

        return {
          moduleName: moduleName,
          routineName: programPointer['routinename'],
          BegPosLine: beginPos[0],
          BegPosCol: beginPos[1],
          EndPosLine: endPos[0],
          EndPosCol: endPos[1],
        };
      } catch (error) {
        return API.rejectWithStatus('Get program pointer failed', error, {
          errorCode: ErrorCode.FailedToGetProgramPointer,
        });
      }
    };

    // this.monitorMechUnit = async function (callback = null) {
    //   try {
    //     if (typeof callback !== 'function' && callback !== null)
    //       throw new Error('callback is not a valid function');

    //     const cbMechUnit = function (data) {
    //       this.mechUnit = data;
    //       callback && callback(data);
    //     };
    //     subscribeRes('mechunit', cbMechUnit.bind(this));
    //   } catch (e) {
    //     return API.rejectWithStatus('Failed to subscribe execution state', e);
    //   }
    // };

    /**
     * @typedef startExecutionProps
     * @prop {string} regainMode - valid values: 'continue', 'regain', 'clear' or 'enter_consume'
     * @prop {string} execMode - valid values: 'continue', 'step_in', 'step_over', 'step_out', 'step_backwards', 'step_to_last' or 'step_to_motion'
     * @prop {string} cycleMode - valid values: 'forever', 'as_is' or 'once'
     * @prop {string} condition - valid values: 'none' or 'call_chain'
     * @prop {boolean} stopAtBreakpoint - stop at breakpoint
     * @prop {boolean} enableByTSP - all tasks according to task selection panel
     * @private
     */

    /**
     * This class represents a RAPID Task. It includes some usefull ready-to-use functionalities based on the the OmniCore SDK.
     * This class cannot be directly instantiated. Therefore the {@link API.RAPID.getTask()}
     * method instead.
     * @class Task
     * @memberof API.RAPID
     * @param {object} task A RWS.RAPID task object.
     * @example
     * const task = await API.RAPID.getTask('T_ROB1');
     * const modules = await task.searchModules();
     * @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
     */
    class Task {
      constructor(task) {
        this._task = task;
        this.name = task.getName();
      }

      /**
       * Load a module from the controller HOME files
       * @alias loadModule
       * @memberof API.RAPID.Task
       * @param {string} path Path to the module file in
       * the HOME directory (included extension of the module).
       * @param {boolean} replace If true, it will replace an existing module in RAPID with the same name
       * @note SPOC systems (e.g., RobotWare 8): Write access must be held before loading module. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
       * @note Non-SPOC systems (e.g., RobotWare 7): Edit mastership is handled internally by this interface.
       * @example
       * let url = `${this.path}/${this.name}${this.extension}`;
       * await task.loadModule(url, true);
       */
      async loadModule(path, replace = false) {
        return await loadModule(path, replace, this.name);
      }

      /**
       * Unloads a RAPID module
       * @alias unloadModule
       * @memberof API.RAPID.Task
       * @param {string} module Module's name
       * @example
       * await task.unloadModule("MainModule");
       */
      async unloadModule(module) {
        return await unloadModule(module, this.name);
      }

      /**
       * @typedef stopExecutionProps
       * @prop {RWS.Rapid.StopModes} [stopMode] Stop mode, valid values: 'cycle', 'instruction', 'stop' or 'quick_stop'
       * @prop {RWS.Rapid.UseTSPOptions.normal} [useTSP] Use task selection panel, valid values: 'normal' or 'all_tasks'
       * @memberof API.RAPID.Task
       */

      /**
       * Stops the RAPID execution with the settings given in the parameter object. All or any of the defined parameters can be supplied, if a value is omitted a default value will be used. The default values are:
       * stopMode = 'stop'
       * useTSP = 'normal'
       * @alias stopExecution
       * @memberof API.RAPID.Task
       * @param {stopExecutionProps} props
       * @deprecated since version 1.1.0, use API.RAPID.stopExecution instead
       * @example
       * const task = await API.RAPID.getTask('T_ROB1');
       * await task.stopExecution();
       */
      async stopExecution({stopMode = RWS.Rapid.StopModes.stop, useTSP = RWS.Rapid.UseTSPOptions.normal} = {}) {
        var state = await RWS.Rapid.getExecutionState();
        if (state === RWS.Rapid.ExecutionStates.running) {
          try {
            await RWS.Rapid.stopExecution({
              stopMode,
              useTSP,
            });
          } catch (e) {
            return API.rejectWithStatus('Failed to stop program execution', e, {
              errorCode: ErrorCode.FailedToStopProgramExecution,
            });
          }
        }
      }

      /**
       * @typedef { 'constant' | 'variable' | 'persistent'} VariableSymbolType ;
       * @memberof API.RAPID
       */

      /**
       * @typedef filterVariables
       * @prop {string} [name] Name of the data symbol (not case-sensitive)
       * @prop {VariableSymbolType} [symbolType] Valid values: 'constant', 'variable', 'persistent' (array with multiple values is supported)
       * @prop {string} [dataType] Type of the data, e.g. 'num'(only one data type is supported)
       * i.e. a type declaration that has no declared variable will not be returned when this flag is set true.
       * @memberof API.RAPID
       */

      /**
       * Searchs for variables contained in a module
       * @alias searchVariables
       * @memberof API.RAPID.Task
       * @param {string} module - Module where the search takes place
       * @param {boolean} [isInUse] Only return symbols that are used in a Rapid program,
       * @param {filterVariables} [filter] See {@link filterVariables}
       * @returns {Promise<RWS.Rapid.SymbolSearchResult[]>}
       * @example
       * // list all num variables in MainModule of task T_ROB1
       * const task = await API.RAPID.getTask('T_ROB1');
       * await task.searchVariables("MainModule", false, {dataType: "num"});
       */
      async searchVariables(module = null, isInUse = false, filter = {}) {
        let searchFilter = {module, isInUse};
        let types;
        if (Object.prototype.hasOwnProperty.call(filter, 'symbolType')) {
          types = Array.isArray(filter.symbolType)
            ? filter.symbolType.reduce((all, entry, idx, arr) => {
                if (entry === 'rapidData') {
                  arr.splice(1); // eject early
                  return RWS.Rapid.SymbolTypes[entry];
                }
                return entry === 'constant' || entry === 'variable' || entry === 'persistent'
                  ? all + RWS.Rapid.SymbolTypes[entry]
                  : all;
              }, 0)
            : RWS.Rapid.SymbolTypes[filter.symbolType];
        } else {
          types = RWS.Rapid.SymbolTypes['rapidData:'];
        }
        if (types !== undefined) {
          searchFilter.symbolType = types;
        }

        searchFilter.task = this.name;
        if (Object.prototype.hasOwnProperty.call(filter, 'name')) searchFilter.name = filter.name;
        if (Object.prototype.hasOwnProperty.call(filter, 'dataType')) searchFilter.dataType = filter.dataType;
        return searchSymbol(searchFilter);
      }

      /**
       * Searchs for available modules
       * @alias searchModules
       * @memberof API.RAPID.Task
       * @param {boolean} [isInUse] Only return symbols that are used in a RAPID program,
       * i.e. a type declaration that has no declared variable will not be returned when this flag is set to true.
       * @param {string} [filter] Only symbols in the string pattern (not case-sensitive)
       * @returns {Promise<RWS.Rapid.SymbolSearchResult[]>}
       * @example
       * const task = await API.RAPID.getTask('T_ROB1');
       * await task.searchModules()
       */
      async searchModules(isInUse = false, filter = '') {
        return searchSymbol({
          task: this.name,
          isInUse: isInUse,
          symbolType: RWS.Rapid.SymbolTypes.module,
          name: filter,
        });
      }

      /**
       * Searchs for procedures contained in a module
       * @alias searchProcedures
       * @memberof API.RAPID.Task
       * @param {string} module Module where the search takes place
       * @param {boolean} [isInUse] Only return symbols that are used in a RAPID program, i.e. a type declaration that has no declared variable will not be returned when this flag is set true.
       * @param {string} [filter] Only symbols in the string pattern (not case-sensitive)
       * @returns {Promise<RWS.Rapid.SymbolSearchResult[]>}
       * @example
       * const task = await API.RAPID.getTask('T_ROB1');
       * await task.searchProcedures()
       */
      async searchProcedures(module = null, isInUse = false, filter = '') {
        return searchSymbol({
          task: this.name,
          module: module,
          isInUse: isInUse,
          symbolType: RWS.Rapid.SymbolTypes.procedure,
          name: filter,
        });
      }

      /**
       * @typedef { 'procedure' | 'function' | 'trap'} RoutineSymbolType ;
       * @memberof API.RAPID
       */

      /**
       * @typedef filterRoutines
       * @prop {string} [name] Name of the data symbol (not case-sensitive)
       * @prop {RoutineSymbolType} [symbolType] Valid values: 'procedure', 'function', 'trap' , 'routine'(array with multiple values is supported)
       * @memberof API.RAPID
       */

      /**
       * Search for routines contained in a module
       * @alias searchRoutines
       * @memberof API.RAPID.Task
       * @param {string} module Module where the search takes place
       * @param {boolean} [isInUse] Only return symbols that are used in a Rapid program,
       * @param {filterRoutines} [filter] See {@link filterRoutines}
       *
       * @returns {Promise<RWS.Rapid.SymbolSearchResult[]>}
       * @example
       * const task = await API.RAPID.getTask('T_ROB1');
       * await task.searchRoutines()
       */
      async searchRoutines(module = null, isInUse = false, filter = {}) {
        let types;
        if (Object.prototype.hasOwnProperty.call(filter, 'symbolType')) {
          types = Array.isArray(filter.symbolType)
            ? filter.symbolType.reduce((all, entry, idx, arr) => {
                if (entry === 'routine') {
                  arr.splice(1); // eject early
                  return RWS.Rapid.SymbolTypes[entry];
                }
                return entry === 'procedure' || entry === 'function' || entry === 'trap'
                  ? all + RWS.Rapid.SymbolTypes[entry]
                  : all;
              }, 0)
            : RWS.Rapid.SymbolTypes[filter.symbolType];
        } else {
          types = RWS.Rapid.SymbolTypes['routine'];
        }

        return searchSymbol({
          task: this.name,
          module: module,
          isInUse: isInUse,
          symbolType: types,
          name: filter.name,
        });
      }

      /**
       * Gets the value of a RAPID variable.
       * @alias getValue
       * @memberof API.RAPID.Task
       * @param {string} module The module containing the variable.
       * @param {string} variable The variable name
       * @returns {Promise<object>}
       * @example
       * // get the variable value of RAPID variable n1 in MainModule
       * const task = await API.RAPID.getTask('T_ROB1');
       * await task.getValue("MainModule", "n1")
       */
      async getValue(module, variable) {
        try {
          var data = await this._task.getData(module, variable);
          // const properties = await data.getProperties();
          return await data.getValue();
        } catch (e) {
          return API.rejectWithStatus(
            `Variable ${variable} not found at ${this._task.getName()} : ${module} module.`,
            e,
          );
        }
      }

      /**
       * Sets the value of a variable
       * @alias setValue
       * @memberof API.RAPID.Task
       * @param {string} module Module containing the variable
       * @param {string} variable Variable name
       * @param {object} value Value of the variable
       * @returns {Promise<object>}
       * @todo Valiation of value not yet applied
       * @example
       * // set the variable value of RAPID variable n1 in MainModule
       * const task = await API.RAPID.getTask('T_ROB1');
       * await task.setValue("MainModule", "aa",7);
       */
      async setValue(module, variable, value) {
        try {
          var data = await this._task.getData(module, variable);
          await data.setValue(value);
        } catch (err) {
          return API.rejectWithStatus(`Failed to set variable ${module}:${variable}.`, err, {
            errorCode: ErrorCode.FailedToSetVariableValue,
            msgParams: {name: variable, value},
          });
        }
      }

      /**
       * Gets a RWS Data object variable
       * @alias getVariableInstance
       * @memberof API.RAPID.Task
       * @param {string} module The module containing the variable
       * @param {string} variable The variable name
       * @param {boolean} subscribe
       * @returns {Promise<object>} API.RAPID.Variable object
       * @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
       * @example
       * const task = await API.RAPID.getTask('T_ROB1');
       * await task.getVariableInstance("MainModule", "aa")
       */
      async getVariable(module, variable, subscribe = true) {
        return await getVariableInstance(this.name, module, variable, subscribe);
      }

      /**
       * Gets a module. This will retrieve the properties of the module from the controller and initialize the object.
       * @alias getModule
       * @memberof API.RAPID.Task
       * @param {object} module - The name of the module
       * @returns {Promise<object>} A RWS module object.
       * @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
       * @example
       * const task = await API.RAPID.getTask('T_ROB1');
       * await task.getModule("MainModule")
       */
      async getModule(module) {
        return await this._task.getModule(module);
      }
    }

    /**
     * @typedef VariableProps
     * @prop {string} [taskName] The task name
     * @prop {string} [moduleName] The module name
     * @prop {string} [symbolName] The symbol name
     * @prop {string} [dataType] The symbol's data type
     * @prop {string} [symbolType] The declaration type of the data, valid values:
     *     'constant'
     *     'variable'
     *     'persistent'
     * @prop {number[]} dimensions List of dimensions for arrays
     * @prop {string} [scope] The data's scope, valid values:
     *     'local'
     *     'task'
     *     'global'
     * @prop {string} [dataTypeURL] RWS URL to the data’s type symbol
     * @memberof API.RAPID
     */

    /**
     * This class represents a RAPID variable.
     * @class Variable
     * @memberof API.RAPID
     * @param {RWS.Rapid.Data} variable
     * @param {VariableProps} props See {@link VariableProps}
     * @property {object} var Object returned by RWS.Rapid.getData().
     * @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
     */
    class Variable extends API.Events {
      constructor(variable, props) {
        super();
        this.props = props;
        this.var = variable;
        this.callbacks = [];
        this._subscribed = false;
      }

      get name() {
        return this.props.symbolName;
      }

      /**
       * @deprecated avoid providing an async getter as it can cause unexpected behavior. Use getValue() instead.
       */
      get value() {
        return (async () => {
          try {
            await this.var.fetch();
            return await this.var.getValue();
          } catch (e) {
            return API.rejectWithStatus(`Failed to get value of variable "${this.name}"`, e, {
              errorCode: ErrorCode.FailedToGetVariableValue,
            });
          }
        })();
      }

      set value(v) {
        this.var && this.var.setValue(v);
      }

      async getValue() {
        try {
          // await this.var.fetch();
          return await this.var.getValue();
        } catch (e) {
          return API.rejectWithStatus(`Failed to get value of variable "${this.name}"`, e, {
            errorCode: ErrorCode.FailedToGetVariableValue,
          });
        }
      }

      async setValue(v) {
        try {
          if (this.type === 'num' && typeof v === 'string') v = Number.parseInt(v);
          return this.var && (await this.var.setValue(v));
        } catch (e) {
          return API.rejectWithStatus(`Failed to set value of variable ${this.name}`, e, {
            errorCode: ErrorCode.FailedToSetVariableValue,
            msgParams: {name: this.name, value: v},
          });
        }
      }

      async setRawValue(v) {
        try {
          if (typeof v != 'string') throw new Error('Incorrect raw value');
          return this.var && (await this.var.setRawValue(v));
        } catch (e) {
          return API.rejectWithStatus(`Failed to set value of variable ${this.name}`, e, {
            errorCode: ErrorCode.FailedToSetVariableValue,
            msgParams: {name: this.name, value: v},
          });
        }
      }

      async getRawValue() {
        try {
          // await this.var.fetch();
          return await this.var.getRawValue();
        } catch (e) {
          return API.rejectWithStatus(`Failed to get value of variable "${this.name}"`, e, {
            errorCode: ErrorCode.FailedToGetVariableValue,
          });
        }
      }

      get type() {
        return this.props.dataType;
      }

      /**
       * Returns the declaration type of the data, valid values:
       * 'constant'
       * 'variable'
       * 'persistent'
       */
      get symbolType() {
        return this.props.symbolType;
      }

      /**
       * Subscribes to a RAPID variable
       * @alias subscribe
       * @param {boolean} raiseInitial Raises an event after subscription
       * @returns {undefined | Promise<{}>} Undefined at success, reject Promise at failure.
       * @memberof API.RAPID.Variable
       */
      async subscribe(raiseInitial = false) {
        try {
          await this._onChanged();

          if (!this._subscrided) {
            await this.var.subscribe(raiseInitial);
            this._subscrided = true;
          }
        } catch (e) {
          return API.rejectWithStatus(`Failed to subscribe variable "${this.name}"`, e, {
            errorCode: ErrorCode.FailedToSubscribeVariable,
          });
        }
      }

      /**
       * Unsubscribes a RAPID variable
       * @alias unsubscribe
       * @returns {undefined | Promise<{}>} Undefined at success, reject Promise at failure.
       * @memberof API.RAPID.Variable
       */
      async unsubscribe() {
        if (this._subscrided) {
          try {
            this._subscribed = false;
            return API.RAPID.unsubscribeVariable(this.props.taskName, this.props.moduleName, this.name);
          } catch (e) {
            return API.rejectWithStatus(`Failed to unsubscribe variable "${this.name}"`, e, {
              errorCode: ErrorCode.FailedToUnsubscribeResource,
            });
          }
        }
      }

      /**
       * Internal callback for variable specific handling. This method is called inside the subscribe method
       * @returns {undefined | Promise<{}>} undefined at success, reject Promise at fail.
       * @private
       */
      async _onChanged() {
        try {
          const cb = async (value) => {
            if (value === undefined) {
              value = await this.var.getValue();
            }
            this.trigger('changed', this._adapter(value));
          };

          this.var.addCallbackOnChanged(cb.bind(this));
        } catch (e) {
          return API.rejectWithStatus(`Failed to add callback on changed for "${this.name}"`, e, {
            errorCode: ErrorCode.FailedToAddCallbackOnChanged,
          });
        }
      }

      _adapter(value) {
        return value;
      }

      /**
       * Add a callback function to be executed when the variable value changes
       * @alias onChanged
       * @param {*} callback The callback function that is called when the variable value changes.
       * @memberof API.RAPID.Variable
       */
      onChanged(callback) {
        this.on('changed', callback);
      }
    }

    /**
     * This class represents a RAPID variable in "string" type.
     * @class VariableString
     * @extends API.RAPID.Variable
     * @memberof API.RAPID
     * @param {RWS.Rapid.Data} variable
     * @param {VariableProps} props See {@link VariableProps}
     * @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
     */
    class VariableString extends Variable {
      async getValue() {
        const value = await super.getValue();
        return value;
      }

      async setValue(value) {
        await super.setValue(value);
      }

      _adapter(value) {
        return value.replace(/"$/, '').replace(/^"/, '');
      }
    }

    /**
     * This class represents a RAPID variable in "bool" type.
     * @class VariableBool
     * @extends API.RAPID.Variable
     * @memberof API.RAPID
     * @param {RWS.Rapid.Data} variable
     * @param {VariableProps} props See {@link VariableProps}
     * @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
     */
    class VariableBool extends Variable {
      async getValue() {
        const value = await super.getValue();
        return value;
      }

      async setValue(value) {
        await super.setValue(value);
      }

      _adapter(value) {
        if (typeof value == 'string') {
          return JSON.parse(
            value.replace(/TRUE/g, 'true').replace(/FALSE/g, 'false').replace(/"$/, '').replace(/^"/, ''),
          );
        }
        return value;
      }
    }

    /**
     * This class represents a RAPID variable in "num" or "dnum" type.
     * @class VariableNum
     * @extends API.RAPID.Variable
     * @memberof API.RAPID
     * @param {RWS.Rapid.Data} variable
     * @param {VariableProps} props See {@link VariableProps}
     * @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
     */
    class VariableNum extends Variable {
      async getValue() {
        const value = await super.getValue();
        return this._getArrayItems(value);
      }

      async setValue(value) {
        const setArrayItems = async function (array, variable, indices = []) {
          array.forEach(async (element, index) => {
            if (Array.isArray(element)) {
              await setArrayItems(element, variable, [...indices, index + 1]);
            } else {
              await variable.setArrayItem(Number(element), ...[...indices, index + 1]);
            }
          });
        };

        if (typeof value === 'string') value = JSON.parse(value);

        if (Array.isArray(value)) {
          await setArrayItems(value, this.var);
        } else {
          await super.setValue(Number(value));
        }
      }

      _adapter(value) {
        return this._getArrayItems(value);
      }

      _getArrayItems(value) {
        if (typeof value === 'string') value = JSON.parse(value);
        if (Array.isArray(value)) {
          const ret = value.map((v) => this._getArrayItems(v));
          return ret;
        } else {
          return Number(value);
        }
      }
    }

    /**
     * This class represents a RAPID variable in "robtarget" type.
     * @class VariableRobTarget
     * @extends API.RAPID.Variable
     * @memberof API.RAPID
     * @param {RWS.Rapid.Data} variable
     * @param {VariableProps} props See {@link VariableProps}
     * @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
     */
    class VariableRobTarget extends Variable {
      async getValue() {
        const value = await super.getValue();
        // const parsedRobTarget = API.RWS.MOTIONSYSTEM.parseRobTarget(value);
        // return parsedRobTarget;
        return value;
      }

      async setValue(value) {
        await super.setValue(value);
      }

      _adapter(value) {
        return value;
      }
    }

    /**
     * This class represents a RAPID variable in "jointtarget" type.
     * @class VariableJointTarget
     * @extends API.RAPID.Variable
     * @memberof API.RAPID
     * @param {RWS.Rapid.Data} variable
     * @param {VariableProps} props See {@link VariableProps}
     * @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
     */
    class VariableJointTarget extends Variable {
      async getValue() {
        const value = await super.getValue();
        // const parsedJointTarget = API.RWS.MOTIONSYSTEM.parseJointTarget(value);
        // return parsedJointTarget;
        return value;
      }

      async setValue(value) {
        await super.setValue(value);
      }

      _adapter(value) {
        return value;
      }
    }

    const AtomicRapidSymbolTypeList = ['num', 'dnum', 'string', 'bool', 'byte'];
    const getFirstLevelComponents = function (rapid) {
      const result = [];
      let current = '';
      let level = 0;
      rapid = rapid.slice(1, -1);
      try {
        for (let i = 0; i < rapid.length; i++) {
          const char = rapid[i];

          if (char === '[') {
            if (level === 0 && current.trim() !== '') {
              result.push(current.trim());
              current = '';
            }
            level++;
            current += char;
          } else if (char === ']') {
            level--;
            current += char;
            if (level === 0) {
              result.push(current.trim());
              current = '';
            }
          } else if (char === ',' && level === 0) {
            if (current.trim() !== '') {
              result.push(current.trim());
            }
            current = '';
          } else {
            current += char;
          }
        }
        if (current) {
          result.push(current);
        }
        return result;
      } catch (e) {
        return API.rejectWithStatus('Failed to parse RAPID array or RECORD data', e, {
          errorCode: ErrorCode.FailedToParseRapidData,
        });
      }
    };

    this.parseRapidArrayValue = function (rapid, dimension) {
      try {
        let result = rapid;
        if (!rapid || typeof rapid !== 'string') throw new Error('Incorrect rapid value');
        if (dimension.length > 3) throw new Error('RAPID does not support array with dimension more than 3');

        for (let i = 0; i < dimension.length; i++) {
          if (dimension[i] < 0) throw new Error('Incorrect dimension number');

          let firstLevel = getFirstLevelComponents(result);
          if (firstLevel.length == 0 || firstLevel.length < dimension[i] + 1) throw new Error('Incorrect array format');

          result = firstLevel[dimension[i]];
        }
        return result;
      } catch (e) {
        return API.rejectWithStatus('Failed to parse rapid array value', e, {
          errorCode: ErrorCode.FailedToParseRapidData,
        });
      }
    };
    this.generateRapidArrayValue = function (v, rawValue, dimension) {
      try {
        if (!rawValue || typeof rawValue !== 'string') {
          throw new Error('Incorrect raw value');
        }
        if (!dimension || dimension.length === 0) {
          throw new Error('Dimension cannot be empty');
        }
        if (dimension.length > 3) {
          throw new Error('RAPID does not support array with dimension more than 3');
        }
        for (let i = 0; i < dimension.length; i++) {
          if (dimension[i] < 0) {
            throw new Error('Incorrect dimension number');
          }
        }

        const replaceAtDimension = (currentValue, dims, newValue, currentDim = 0) => {
          const levels = getFirstLevelComponents(currentValue);

          if (currentDim === dims.length - 1) {
            if (dims[currentDim] >= levels.length) {
              throw new Error('Dimension index out of bounds');
            }
            if (levels[dims[currentDim]].startsWith('"') && levels[dims[currentDim]].endsWith('"')) {
              newValue = `"${newValue}"`;
            }
            levels[dims[currentDim]] = newValue;
            return `[${levels.join(',')}]`;
          } else {
            if (dims[currentDim] >= levels.length) {
              throw new Error('Dimension index out of bounds');
            }
            const updatedLevel = replaceAtDimension(levels[dims[currentDim]], dims, newValue, currentDim + 1);
            levels[dims[currentDim]] = updatedLevel;
            return `[${levels.join(',')}]`;
          }
        };

        return replaceAtDimension(rawValue, dimension, v);
      } catch (e) {
        return API.rejectWithStatus('Failed to generate array value', e, {
          errorCode: ErrorCode.FailedToGenerateRapidArray,
        });
      }
    };

    /**
     * This class represents a RAPID RECORD data type.
     * This class includes some necessary properties to modelize RAPID RECORD and methods to handle RAPID RECORD, such as the methods to parse RECORD's data structure.
     * @class RecordData
     * @extends API.RAPID
     * @memberof API.RAPID
     * @param {string} type RECORD type
     * @param {string} name RECORD component name
     * @param {string} blockUrl The URL to the source RECORD.
     * @param {boolean} isAtom True if the RECORD is one of the RAPID atomic data types: num, bool, string, dnum, byte
     * @param {array<API.RAPID.RecordData>} children The child parts owned by the RECORD
     * @param {boolean} isUnknownAtom True if the RECORD is in atomic type that does not included by any known ones.
     * @param {any} value The value of the RECORD data
     */
    class RecordData {
      constructor(type, blockUrl, children = [], name = '', value = null) {
        this.type = type;
        this.blockUrl = blockUrl;
        this.children = children;
        this.isAtom = AtomicRapidSymbolTypeList.includes(this.type);
        this.name = name;
        this.setValue(value);
      }
      addChild(child) {
        this.children.push(child);
      }
      fromJSON(json) {
        const data = typeof json === 'string' ? JSON.parse(json) : json;
        const instance = new RecordData(
          data.type,
          data.blockUrl,
          data.children.map((child) => this.fromJSON(child)),
          data.name,
          data.value,
        );
        return instance;
      }

      setValue(value) {
        if (this.isAtom) {
          switch (this.type) {
            case 'num':
            case 'dnum':
              this.value = typeof value != 'undefined' ? value : 0;
              break;
            case 'string':
              this.value = typeof value != 'undefined' ? value : '""';
              break;
            case 'bool':
              this.value = typeof value != 'undefined' ? value : false;
              break;
            default:
              // 1. unknown RAPID value-type built-in atomic types
              // 2. atomic type from reaching max recursive depth
              this.value = typeof value != 'undefined' ? value : this.isUnknownAtom ? '' : '[]';
              break;
          }
        } else if (value) {
          this.value = value;
        }
      }
      getRapidValue() {
        if (this.isAtom) {
          if (this.type == 'string') {
            if (this.value.startsWith('"') && this.value.endsWith('"')) {
              return this.value;
            } else {
              return `"${this.value}"`;
            }
          }
          return `${this.value}`;
        }
        let rapidValue = '';
        for (let i = 0; i < this.children.length; i++) {
          rapidValue += (i == 0 ? '' : ',') + this.children[i].getRapidValue();
        }
        this.value = '[' + rapidValue + ']';
        return this.value;
      }
      parseFromRapid(rapid) {
        if (!rapid) return;
        this.setValue(rapid);
        if (this.children.length > 0) {
          const firstLevelComponents = getFirstLevelComponents(rapid);
          if (!firstLevelComponents || firstLevelComponents.length !== this.children.length) {
            return API.rejectWithStatus(
              'parse record data failed',
              {},
              {
                errorCode: ErrorCode.FailedToParseRapidData,
              },
            );
          }
          for (let i = 0; i < firstLevelComponents.length; i++) {
            this.children[i].parseFromRapid(firstLevelComponents[i]);
          }
        }
      }
      setValueByNamePath(namePath, value) {
        if (!namePath || namePath.length == 0) {
          this.value = value;
          return;
        }
        let current = this;
        for (let i = 0; i < namePath.length; i++) {
          const name = namePath[i];
          if (!current.children || !current.children.length) throw new Error('Cannot set value, value path is invalid');
          const child = current.children.find((c) => c.name === name);
          if (!child) throw new Error('Cannot set value, value path is invalid');
          if (i === namePath.length - 1) {
            child.value = value;
          }
          current = child;
        }
      }
      getValueByNamePath(namePath) {
        if (!namePath || namePath.length == 0) return this.value;
        let current = this;
        for (let i = 0; i < namePath.length; i++) {
          const name = namePath[i];
          if (!current.children || !current.children.length) return undefined;
          const child = current.children.find((c) => c.name === name);
          if (!child) return undefined;
          if (i === namePath.length - 1) {
            return child.value;
          }
          current = child;
        }
        return undefined;
      }
      getChildrenByNamePath(namePath) {
        if (!namePath.length) return undefined;
        let current = this;
        for (let i = 0; i < namePath.length; i++) {
          const name = namePath[i];
          if (!current.children || !current.children.length) return undefined;
          const child = current.children.find((c) => c.name === name);
          if (!child) return undefined;
          if (i === namePath.length - 1) {
            return child;
          }
          current = child;
        }
        return undefined;
      }
    }
    this.getRecordData = function (type, blockUrl, children = [], name = '', value = null) {
      return new RecordData(type, blockUrl, children, name, value);
    };

    const ReservedDataTypes = {
      POS: 'pos',
      ORIENT: 'orient',
      EXTJOINT: 'extjoint',
      ROBJOINT: 'robjoint',
      POSE: 'pose',
      JOINTTARGET: 'jointtarget',
      CONFDATA: 'confdata',
      ROBTARGET: 'robtarget',
      LOADDATA: 'loaddata',
      TOOLDATA: 'tooldata',
      WOBJDATA: 'wobjdata',
      ZONEDATA: 'zonedata',
      SPEEDDATA: 'speeddata',
    };
    const AtomicRapidSymbolTypes = {
      NUM: 'num',
      DNUM: 'dnum',
      STR: 'string',
      BOOL: 'bool',
      BYTE: 'byte',
    };
    const SymbolValueTypes = {
      SemiValue: 'semival',
      Value: 'val',
      NoValue: 'nonval',
    };
    const MaxDepth = 3;

    this.getRecordDataStructure = async function (type, typeUrl = '', name = '', currentDepth = 0, task = 'T_ROB1') {
      let exturl = 'symbols/search';
      let blockurl = typeUrl ? typeUrl : `RAPID/${task}/` + type;
      let searchFlag = false;
      let hasError = false;
      let dataStruct = new RecordData(type, blockurl);

      dataStruct.name = name;
      if (AtomicRapidSymbolTypeList.includes(type)) {
        dataStruct.isAtom = true;
        dataStruct.isUnknownAtom = false;
      } else if (Object.values(ReservedDataTypes).includes(type)) {
        // check if reserved data type: reduce RWS requests
        let reservedStruc = getDataStructure(type);
        if (!reservedStruc) throw new Error('get reserved data structure failed');
        dataStruct.isAtom = reservedStruc.isAtom;
        dataStruct.type = reservedStruc.type;
        dataStruct.blockUrl = reservedStruc.blockUrl;
        dataStruct.children = reservedStruc.children;
        dataStruct.isUnknownAtom = reservedStruc.isUnknownAtom;
      } else {
        try {
          // check if recursive depth overreaches the max depth
          if (currentDepth > MaxDepth) {
            throw new Error('The nested level of custom data structure reaches the Max level');
          } else {
            currentDepth = currentDepth + 1;
            dataStruct.isAtom = false;
            // check if custom data is value type: non-value or semi-value type cannot be edited
            let property;
            try {
              property = await API.RWS.RAPID.getDataTypeProperty(blockurl);
            } catch (error) {
              // check if RAPID built-in type
              blockurl = 'RAPID/' + type;
              property = await API.RWS.RAPID.getDataTypeProperty(blockurl);
              dataStruct.blockUrl = blockurl;
            }
            if (!equalsIgnoreCase(property.valtyp, SymbolValueTypes.Value)) throw new Error('Not value type');
            // check if unknown RAPID value-type atomic type: except num,dnum,string,bool
            if (property.symtyp && equalsIgnoreCase(property.symtyp, 'atm')) {
              dataStruct.isAtom = true;
              dataStruct.type = type;
              dataStruct.blockUrl = blockurl;
              dataStruct.children = [];
              dataStruct.isUnknownAtom = true;
            } else {
              while (!searchFlag) {
                let searchedResult = await API.RWS.RAPID.getDataTypeStructure(blockurl, exturl);
                let symbols = searchedResult['_embedded']['resources'];
                // The order of components in the RWS response is exactly the reverse of the actual component order
                for (let i = symbols.length - 1; i >= 0; i--) {
                  let child = await this.getRecordDataStructure(
                    symbols[i].dattyp,
                    symbols[i].typurl,
                    symbols[i].name,
                    currentDepth,
                    task,
                  );
                  if (!child) throw new Error('get custom data structure failed');
                  dataStruct.addChild(child);
                }
                if (typeof searchedResult._links.next == 'undefined') {
                  searchFlag = true;
                } else {
                  exturl = searchedResult._links.next.href;
                }
              }
            }
          }
        } catch (error) {
          searchFlag = true;
          hasError = true;
          Logger.e(logModule, ErrorCode.FailedToGetRecordDataStructure, error);
          // throw 'get custom data structure failed'
        }
      }
      return hasError ? null : dataStruct;
    };
    const equalsIgnoreCase = (textA, textB) => {
      if (textA && textB) {
        return textA.toLowerCase() == textB.toLowerCase();
      }
      return false;
    };
    const getDataStructure = function (type) {
      let dataStruct;
      switch (type) {
        case ReservedDataTypes.POS:
          dataStruct = generatePosDataStructure();
          break;
        case ReservedDataTypes.ORIENT:
          dataStruct = generateOrientDataStructure();
          break;
        case ReservedDataTypes.EXTJOINT:
          dataStruct = generateExtjointDataStructure();
          break;
        case ReservedDataTypes.ROBJOINT:
          dataStruct = generateRobjointDataStructure();
          break;
        case ReservedDataTypes.POSE:
          dataStruct = generatePoseDataStructure();
          break;
        case ReservedDataTypes.JOINTTARGET:
          dataStruct = {
            type: ReservedDataTypes.JOINTTARGET,
            name: '',
            blockUrl: 'RAPID/jointtarget',
            isAtom: false,
            children: [generateRobjointDataStructure('robax'), generateExtjointDataStructure('extax')],
            isUnknownAtom: false,
          };
          break;
        case ReservedDataTypes.CONFDATA:
          dataStruct = generateConfDataStructure();
          break;
        case ReservedDataTypes.ROBTARGET:
          dataStruct = {
            type: ReservedDataTypes.ROBTARGET,
            name: '',
            blockUrl: 'RAPID/robtarget',
            isAtom: false,
            children: [
              generatePosDataStructure('trans'),
              generateOrientDataStructure('rot'),
              generateConfDataStructure('robconf'),
              generateExtjointDataStructure('extax'),
            ],
            isUnknownAtom: false,
          };
          break;
        case ReservedDataTypes.LOADDATA:
          dataStruct = generateLoadDataStructure();
          break;
        case ReservedDataTypes.TOOLDATA:
          dataStruct = {
            type: ReservedDataTypes.TOOLDATA,
            name: '',
            blockUrl: 'RAPID/tooldata',
            isAtom: false,
            children: [
              generateCommonDataStructure(AtomicRapidSymbolTypes.BOOL, 'robhold'),
              generatePoseDataStructure('tframe'),
              generateLoadDataStructure('tload'),
            ],
            isUnknownAtom: false,
          };
          break;
        case ReservedDataTypes.WOBJDATA:
          dataStruct = {
            type: ReservedDataTypes.WOBJDATA,
            name: '',
            blockUrl: 'RAPID/wobjdata',
            isAtom: false,
            children: [
              generateCommonDataStructure(AtomicRapidSymbolTypes.BOOL, 'robhold'),
              generateCommonDataStructure(AtomicRapidSymbolTypes.BOOL, 'ufprog'),
              generateCommonDataStructure(AtomicRapidSymbolTypes.STR, 'ufmec'),
              generatePoseDataStructure('uframe'),
              generatePoseDataStructure('oframe'),
            ],
            isUnknownAtom: false,
          };
          break;
        case ReservedDataTypes.ZONEDATA:
          dataStruct = {
            type: ReservedDataTypes.ZONEDATA,
            name: '',
            blockUrl: 'RAPID/zonedata',
            isAtom: false,
            children: [
              generateCommonDataStructure(AtomicRapidSymbolTypes.BOOL, 'finep'),
              generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'pzone_tcp'),
              generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'pzone_ori'),
              generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'pzone_eax'),
              generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'pzone_ori'),
              generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'pzone_leax'),
              generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'pzone_reax'),
            ],
            isUnknownAtom: false,
          };
          break;
        case ReservedDataTypes.SPEEDDATA:
          dataStruct = {
            type: ReservedDataTypes.SPEEDDATA,
            name: '',
            blockUrl: 'RAPID/speeddata',
            isAtom: false,
            children: [
              generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'v_tcp'),
              generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'v_ori'),
              generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'v_leax'),
              generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'v_reax'),
            ],
            isUnknownAtom: false,
          };
          break;
        default:
          break;
      }
      return dataStruct;
    };
    this.getDataStructure = getDataStructure;

    const generateCommonDataStructure = function (type, name) {
      if (!AtomicRapidSymbolTypeList.includes(type)) return null;
      return {
        type: type,
        name: name,
        blockUrl: 'RAPID/' + type,
        isAtom: true,
        children: [],
        isUnknownAtom: false,
      };
    };

    const generatePosDataStructure = function (name = '') {
      return {
        type: ReservedDataTypes.POS,
        name: name,
        blockUrl: 'RAPID/pos',
        isAtom: false,
        children: [
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'x'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'y'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'z'),
        ],
        isUnknownAtom: false,
      };
    };

    const generateOrientDataStructure = function (name = '') {
      return {
        type: ReservedDataTypes.ORIENT,
        name: name,
        blockUrl: 'RAPID/orient',
        isAtom: false,
        children: [
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'q1'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'q2'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'q3'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'q4'),
        ],
        isUnknownAtom: false,
      };
    };

    const generatePoseDataStructure = function (name = '') {
      return {
        type: ReservedDataTypes.POSE,
        name: name,
        blockUrl: 'RAPID/pose',
        isAtom: false,
        children: [generatePosDataStructure('trans'), generateOrientDataStructure('rot')],
        isUnknownAtom: false,
      };
    };

    const generateExtjointDataStructure = function (name = '') {
      return {
        type: ReservedDataTypes.EXTJOINT,
        name: name,
        blockUrl: 'RAPID/extjoint',
        isAtom: false,
        children: [
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'eax_a'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'eax_b'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'eax_c'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'eax_d'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'eax_e'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'eax_f'),
        ],
        isUnknownAtom: false,
      };
    };

    const generateRobjointDataStructure = function (name = '') {
      return {
        type: ReservedDataTypes.ROBJOINT,
        name: name,
        blockUrl: 'RAPID/robjoint',
        isAtom: false,
        children: [
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'rax_1'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'rax_2'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'rax_3'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'rax_4'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'rax_5'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'rax_6'),
        ],
        isUnknownAtom: false,
      };
    };

    const generateConfDataStructure = function (name = '') {
      return {
        type: ReservedDataTypes.CONFDATA,
        name: name,
        blockUrl: 'RAPID/confdata',
        isAtom: false,
        children: [
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'cf1'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'cf4'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'cf6'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'cfx'),
        ],
        isUnknownAtom: false,
      };
    };

    const generateLoadDataStructure = function (name = '') {
      return {
        type: ReservedDataTypes.LOADDATA,
        name: name,
        blockUrl: 'RAPID/loaddata',
        isAtom: false,
        children: [
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'mass'),
          generatePosDataStructure('cog'),
          generateOrientDataStructure('aom'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'ix'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'iy'),
          generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'iz'),
        ],
        isUnknownAtom: false,
      };
    };

    /**
     * Creates a variable instance.
     * @alias createVariableInstance
     * @memberof API.RAPID
     * @param {string} module The name of the module where the variable locates..
     * @param {string} name The variable name.
     * @param {string} task The task name.
     * @example
     * let variableInstance = await API.RAPID.createVariableInstance("MainModule","aa");
     */
    this.createVariableInstance = async function (module, name, task = 'T_ROB1') {
      const instanceKey = `${task}_${module}_${name}`;
      if (this.variableInstanceMap.has(instanceKey)) {
        const instance = this.variableInstanceMap.get(instanceKey);
        // The underlying `Data` class uses a cache;
        // if no subscription is enabled, it will not automatically update the values,
        // so you need to update them manually here.
        await instance.var.fetch();
        return instance;
      }
      try {
        var v = await RWS.Rapid.getData(task, module, name);
      } catch (error) {
        return API.rejectWithStatus(`Failed to get the variable: ${name}`, error, {
          errorCode: ErrorCode.FailedToGetBindedVariable,
        });
      }

      const p = await v.getProperties();

      let variable;

      if (p.dataType === 'string') variable = new VariableString(v, p);
      else if (p.dataType === 'bool') variable = new VariableBool(v, p);
      else if (p.dataType === 'num' || p.dataType === 'dnum') variable = new VariableNum(v, p);
      else if (p.dataType === 'robtarget') variable = new VariableRobTarget(v, p);
      else if (p.dataType === 'jointtarget') variable = new VariableJointTarget(v, p);
      else variable = new Variable(v, p);
      this.variableInstanceMap.set(instanceKey, variable);
      return variable;
    };

    /**
     * Gets the data type of a RAPID variable
     * @alias getVariableType
     * @memberof API.RAPID
     * @param {string} module The name of the module where the variable locates.
     * @param {string} name The variable name
     * @param {string} task The task name
     * @example
     * let type = await API.RAPID.getVariableType("MainModule","aa");
     */
    this.getVariableType = async function (module, name, task = 'T_ROB1') {
      try {
        const v = await RWS.Rapid.getData(task, module, name);
        const p = await v.getProperties();
        return p.dataType;
      } catch (error) {
        return API.rejectWithStatus(`Failed to get variable ${name}'s type`, error, {
          errorCode: ErrorCode.FailedToGetVariableProperty,
        });
      }
    };

    /**
     * Gets the properties of a variable
     * @alias getVariableProperties
     * @memberof API.RAPID
     * @param {string} module The name of the module where the variable locates.
     * @param {string} name The variable name
     * @param {string} task The task name
     * @example
     * let props = await API.RAPID.getVariableProperties("MainModule","aa");
     */
    this.getVariableProperties = async function (module, name, task = 'T_ROB1') {
      try {
        const v = await RWS.Rapid.getData(task, module, name);
        const p = await v.getProperties();
        return p;
      } catch (error) {
        return API.rejectWithStatus(`Failed to get variable ${name}'s properties`, error, {
          errorCode: ErrorCode.FailedToGetVariableProperty,
        });
      }
    };

    /**
     * Get the value of a variable
     * @alias getVariableValue
     * @memberof API.RAPID
     * @param {string} module The name of the module where the variable locates.
     * @param {string} name The variable name
     * @param {string} task The task name
     * @example
     * let value = await API.RAPID.getVariableValue("MainModule","aa");
     */
    this.getVariableValue = async function (module, name, task = 'T_ROB1') {
      try {
        let variable = await API.RAPID.createVariableInstance(module, name, task);
        let value = await variable.getValue();
        return value;
      } catch (e) {
        return API.rejectWithStatus(`Failed to get variable ${name}'s value`, e, {
          errorCode: ErrorCode.FailedToGetVariableValue,
        });
      }
    };

    /**
     * @typedef setVariableValueOptions
     * @prop {boolean} [syncPers=false] If the value is set to true, the variable value is synchronized immediately; otherwise, the variable value will be synchronized until next synchronization action; Set to false if RAPID program is running;
     * @memberof API.RAPID
     */

    /**
     * Sets the value of a RAPID variable
     * @alias setVariableValue
     * @memberof API.RAPID
     * @param {string} module The name of the module where the variable locates.
     * @param {string} name The variable name
     * @param {any} value The value to set
     * @param {string} task The task name
     * @param {setVariableValueOptions} options object
     * @example
     * await API.RAPID.setVariableValue("MainModule","aa", 2,"T_ROB1",{syncPers:true});
     */
    this.setVariableValue = async function (module, name, value, task = 'T_ROB1', {syncPers = false} = {}) {
      try {
        let variable = await API.RAPID.createVariableInstance(module, name, task);
        // For variable whose data type is not string, the value is considered as raw value when the value type is string;
        if (
          typeof value == 'string' &&
          (variable.props.dataType != 'string' ||
            (variable.props.dataType == 'string' && variable.props.dimensions.length > 0))
        ) {
          await variable.setRawValue(value);
        } else {
          await variable.setValue(value);
        }
        if (syncPers) {
          await API.RWS.requestMastership();
          await API.RWS.RAPID.syncPers(module, task);
        }
      } finally {
        if (syncPers) {
          const bHeldEditMasterShip = await API.RWS.checkIfHeldMastership();
          if (bHeldEditMasterShip) {
            await API.RWS.releaseMastership();
          }
        }
      }
    };

    this.unsubscribeVariable = async function (task, module, name) {
      const refName = task + ':' + module + ':' + name;
      let entry = this.subscriptions.get(refName);
      if (entry && --entry.count === 0) {
        await entry.variable.unsubscribe();
        this.subscriptions.delete(refName);
      }
    };

    /**
     * Subscribe to a existing RAPID variable.
     * @alias getVariableInstance
     * @memberof API.RAPID
     * @param {string} task  - RAPID Task in which the variable is contained
     * @param {string} module -RAPID module where the variable is contained
     * @param {string} name - name of RAPID variable
     * @param {boolean} subscribe - subscribe to variable, default is true
     * @returns RWS.RAPID Data object
     * @private
     */
    const getVariableInstance = async (task, module, name, subscribe = true) => {
      if (task && module && name) {
        try {
          const refName = task + ':' + module + ':' + name;
          let entry = this.subscriptions.get(refName);

          if (entry) {
            entry.count++;
            return entry.variable;
          } else {
            let newVariable = await this.createVariableInstance(module, name, task);

            // double check case parallel async subscriptions just happened
            let entry = this.subscriptions.get(refName);
            if (!entry) {
              if (API.isSubscriptionBlocked) {
                Logger.w(WarningType.RWSSubscriptionBlocked, `API.RAPID: Subscription disabled, variable: ${refName}`);
              } else {
                if (subscribe) {
                  this.subscriptions.set(refName, {variable: newVariable, count: 1});
                  if (newVariable.symbolType !== 'constant') newVariable.subscribe();
                }
              }
              return newVariable;
            } else {
              return entry.variable;
            }
          }
        } catch (e) {
          return API.rejectWithStatus(`Failed to subscribe to variable ${name} at ${task}->${module} module.`, e, {
            errorCode: ErrorCode.FailedToSubscribeResource,
          });
        }
      }
    };
    this.getVariable = getVariableInstance;
    this.getVariableInstance = getVariableInstance;

    /**
     * Gets an instance of a API.RAPID.Task class
     * @alias getTask
     * @memberof API.RAPID
     * @param {string} task - Task name
     * @returns {Promise<object>} - API.RAPID.Task
     * @example
     * await API.RAPID.getTask();
     */
    this.getTask = async function (task = 'T_ROB1') {
      const t = await RWS.Rapid.getTask(task);
      return new Task(t);
    };

    /**
     * Load a module from the controller HOME files
     * @alias loadModule
     * @memberof API.RAPID
     * @param {string} path Path to the module file in the HOME directory (included extension of the module).
     * @param {boolean} replace f true, it will replace an existing module in RAPID with the same name
     * @param {string} taskName The name of the task to which the module belongs.
     * @param {boolean} [requestMsh] Edit mastership needs to be requested before loading the module.
     * @note SPOC systems (e.g., RobotWare 8): Write access must be held before loading module. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
     * @note Non-SPOC systems (e.g., RobotWare 7): Edit mastership is handled internally by this interface.
     * @example
     * let url = `${this.path}/${this.name}${this.extension}`;
     * await API.RAPID.loadModule(url, true);
     */
    const loadModule = async function (path, replace = false, taskName = 'T_ROB1', requestMsh = true) {
      await API.RWS.RAPID.loadModule.apply(null, arguments);
    };
    this.loadModule = loadModule;

    /**
     * Gets the RAPID module text.
     * @param {string} moduleName The module name.
     * @param {string} taskName The task name.
     * @returns {Promise<string>} Returns the module text.
     * @example
     * await API.RAPID.getModuleText("MainModule");
     */
    this.getModuleText = async function (moduleName, taskName = 'T_ROB1') {
      const res = await API.RWS.RAPID.getModuleText(moduleName, taskName);
      const moduleText = res['module-text'];
      if (moduleText) return moduleText;

      const filePath = res['file-path'];
      const fileContent = await API.FILESYSTEM.getFile(filePath);
      await API.FILESYSTEM.deleteFile(filePath);
      return fileContent;
    };

    /**
     * Set RAPID module text
     * @note SPOC systems (e.g., RobotWare 8): Write access must be held before modifying module text. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
     * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as edit mastership is handled internally.
     * @param {string} moduleName The module name
     * @param {string} text The text to write
     * @param {string} taskName The task name
     * @example
     * await API.RAPID.setModuleText("Module1",`MODULE Module1\r\nPROC proc1()\r\nTPWrite "123";\r\nENDPROC\r\nENDMODULE`);
     */
    this.setModuleText = async function (moduleName, text, taskName = 'T_ROB1') {
      try {
        await API.RWS.requestMastership('edit');
        try {
          await API.RWS.RAPID.setModuleText(moduleName, text, taskName);
        } catch (e) {
          return API.rejectWithStatus(`Failed to set module ${taskName}:${moduleName} text.`, e, {
            errorCode: ErrorCode.FailedToSetRapidModuleContent,
          });
        }
      } finally {
        await API.RWS.releaseMastership('edit');
      }
    };

    /**
     * Unloads a RAPID module
     * @alias unloadModule
     * @memberof API.RAPID
     * @param {string} moduleName The module name.
     * @param {string} [taskName] The task name.
     * @example
     * await API.RAPID.unloadModule("Module1")
     */
    const unloadModule = async function (moduleName, taskName = 'T_ROB1') {
      // await API.RWS.requestMastership('edit');
      await API.RWS.RAPID.unloadModule.apply(null, arguments);
      // await API.RWS.releaseMastership('edit');
    };
    this.unloadModule = unloadModule;

    /**
     * Prepare actions before executing program / procedure
     */
    const prepareExecution = async function (setPP = true, moduleName = '', procName = '', taskName = 'T_ROB1') {
      // check write access status if SPOC system
      const isSpocSystem = await API.RWS.CONTROLSTATION.isSpocSystem();
      if (isSpocSystem) {
        await API.RWS.CONTROLSTATION.checkIfHeldWriteAccess();
      }
      let exeState = await RWS.Rapid.getExecutionState();
      if (exeState === RWS.Rapid.ExecutionStates.running) {
        throw new Error(ErrorCode.ProgramAlreadyExecuting);
      }

      let ctrlState = await RWS.Controller.getControllerState();
      if (ctrlState !== API.CONTROLLER.STATE.MotorsOn) {
        throw new Error(ErrorCode.FailedToExecuteWithMotorOff);
      }

      let bExecuteProc = moduleName && procName;
      if (bExecuteProc) {
        await API.RAPID.setPPToRoutine(moduleName, procName, taskName);
      } else {
        let pcpRes = await API.RAPID.getProgramPointer();
        if (!pcpRes['moduleName']) {
          if (setPP) {
            await API.RAPID.setPPToMain();
          } else {
            throw new Error(ErrorCode.FailedToExecuteWithoutPointer);
          }
        }
      }
    };

    /**
     * Sets cycle mode
     * @note SPOC systems (e.g., RobotWare 8): Write access must be held before setting cycle mode. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
     * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as edit mastership is handled internally.
     * @alias setCycleMode
     * @memberof API.RAPID
     * @param {string} cycleMode The value can be 'forever' or 'once'.
     * @example
     * await API.RAPID.setCycleMode("once")
     */
    this.setCycleMode = async function (cycleMode = 'once') {
      try {
        await RWS.Mastership.request();
        try {
          await API.RWS.RAPID.setCycleMode(cycleMode);
        } catch (e) {
          return API.rejectWithStatus(`Failed to set cycle mode to ${cycleMode}.`, e, {
            errorCode: ErrorCode.FailedToSetCycleMode,
          });
        }
      } finally {
        await RWS.Mastership.release();
      }
    };

    /**
     * Restore cycle mode after program execution is stopped
     */
    const restoreCycleAfterProgramExecution = async function (preCycleMode, taskName = 'T_ROB1') {
      if (r.isSubscriptionBlocked) {
        Logger.w(WarningType.RWSSubscriptionBlocked, 'API.RAPID: Subscription disabled, monitor execution status');
        return;
      }
      const monitor = RWS.Rapid.getMonitor(RWS.Rapid.MonitorResources.execution, taskName);
      const executionCallback = async function (data) {
        Logger.i(logModule, `program execution status: ${data}`);
        if (data == RWS.Rapid.ExecutionStates.stopped) {
          try {
            // restore cycle mode
            await API.RAPID.setCycleMode(preCycleMode);
            // unsubscribe resource after program stopped
            await monitor.unsubscribe();
          } catch (e) {
            return API.rejectWithStatus(`Failed to recover cycle mode ${preCycleMode} after program execution`, e);
          }
        }
      };
      // subscribe program execution status
      monitor.addCallbackOnChanged(executionCallback.bind(this));
      await monitor.subscribe();
    };

    /**
     * Starts program execution
     * @note SPOC systems (e.g., RobotWare 8): Write access must be held before starting RAPID program execution. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
     * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as edit mastership is handled internally.
     * @alias startExecution
     * @memberof API.RAPID
     * @param {string} cycleMode Cycle mode: forever|as_is|once
     * @param {string} execMode Execution mode: continue|stepin|stepover|stepout|stepback|steplast|stepmotion
     * @param {string} regainMode Regain mode: continue|regain|clear
     * @param {boolean} setPP Set program pointer to main if has no pp
     * @param {string} moduleName Module name
     * @param {string} procName Procedure name
     * @param {string} taskName Task name
     * @example
     * await API.RAPID.startExecution({moduleName: "Wizard", procName: "test1"});
   
     */
    this.startExecution = async function ({
      cycleMode = 'once',
      execMode = 'continue',
      regainMode = 'continue',
      setPP = true,
      moduleName = '',
      procName = '',
      taskName = 'T_ROB1',
    } = {}) {
      let isas = cycleMode == 'as_is';
      let preCycleMode = 'once';
      if (!isas) {
        // get current cycle mode
        preCycleMode = await API.RWS.RAPID.getCycleMode();
        isas = preCycleMode == cycleMode;
      }
      let cycle = cycleMode;
      await prepareExecution(setPP, moduleName, procName, taskName);

      try {
        await RWS.Rapid.startExecution({regainMode: regainMode, executionMode: execMode, cycleMode: cycle});
        if (!isas) {
          await restoreCycleAfterProgramExecution(preCycleMode, taskName);
        }
      } catch (error) {
        // if (error.code) {
        //   const errorCode = error.code;
        //   if (errorCode == '-1073445867') {
        //     // <span class="description">The operation was not allowed for the given user.</span>
        //     throw new Error(ErrorCode.FailedToExecuteWithoutPermissions);
        //   } else if (errorCode == '-1073442807') {
        //     // <span class="description">The robot is not on path and is unable to restart. Regain to or clear path.</span>
        //     throw new Error(ErrorCode.FailedToExecuteOutsideRegain);
        //   }
        // }
        let executionState = await RWS.Rapid.getExecutionState();
        if (executionState == RWS.Rapid.ExecutionStates.running) {
          Logger.i(InfoType.RobotOperation, 'Regain is performing');
        } else {
          throw new Error(ErrorCode.FailedToStartProgramExecution, {cause: error});
        }
      }
    };

    /**
     * @typedef stopExecutionProps
     * @prop {RWS.Rapid.StopModes} [stopMode] Stop mode, valid values: 'cycle', 'instruction', 'stop' or 'quick_stop'
     * @prop {RWS.Rapid.UseTSPOptions.normal} [useTSP] Use task selection panel, valid values: 'normal' or 'all_tasks'
     * @memberof API.RAPID
     */

    /**
     * Stops the RAPID execution with the settings given in the parameter object. All or any of the defined parameters can be supplied, if a value is omitted a default value will be used. The default values are:
     * stopMode = 'stop', and
     * useTSP = 'normal'
     * @alias stopExecution
     * @memberof API.RAPID
     * @param {stopExecutionProps} props
     * @example
     * await API.RAPID.stopExecution();
     */
    this.stopExecution = async function ({
      stopMode = RWS.Rapid.StopModes.stop,
      useTSP = RWS.Rapid.UseTSPOptions.normal,
    } = {}) {
      var state = await RWS.Rapid.getExecutionState();
      if (state === RWS.Rapid.ExecutionStates.running) {
        try {
          await RWS.Rapid.stopExecution({
            stopMode,
            useTSP,
          });
        } catch (e) {
          return API.rejectWithStatus('Failed to stop program execution', e, {
            errorCode: ErrorCode.FailedToStopProgramExecution,
          });
        }
      }
    };

    /**
     * Searchs all routines
     * @alias searchRoutines
     * @memberof API.RAPID
     * @param {boolean} allread If the value is set to true, the non-service routines will also be included.
     * @param {string} taskName The task name
     * @returns {Promise<API.RAPID.routines[]>} A list of API.RAPID.routines
     * @example
     * await task.searchRoutines();
     */
    this.searchRoutines = async function (allread = true, taskName = 'T_ROB1') {
      // check execution status
      let exeState = await RWS.Rapid.getExecutionState();
      if (exeState === RWS.Rapid.ExecutionStates.running) {
        throw new Error(ErrorCode.FailedToOperateDuringExecution, {
          cause: 'Cannot update routines during program execution',
        });
      }
      let searchedResult = null;
      let searchFlag = false;
      let exturl = 'tasks/' + taskName + '/serviceroutine?';
      let result = [];
      try {
        while (!searchFlag) {
          let routineRes = await API.RWS.RAPID.getServiceRoutines(exturl, allread);
          let routines = routineRes['state'];

          for (const key in routines) {
            let url2RoutineArr = routines[key]['url-to-routine'].split('/');
            result.push({
              routineName: routines[key]['routine_name'],
              moduleName: url2RoutineArr[2],
              taskName: url2RoutineArr[1],
              bServiceRoutine: routines[key]['service-routine'] == 'TRUE',
            });
          }

          // Check if there's next page
          if (typeof routineRes._links.next == 'undefined') {
            searchFlag = true;
          } else {
            exturl = routineRes._links.next.href + (allread ? '&' : '');
          }
        }
        return result;
      } catch (error) {
        return API.rejectWithStatus(`Failed to search routine list in task ${taskName}`, error, {
          errorCode: ErrorCode.FailedToSearchRoutines,
        });
      }
    };
  })();

  r.constructedRapid = true;
};

if (typeof API.constructedRapid === 'undefined') {
  factoryApiRapid(API);
}

export default API;