ecosystem-rws.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 Store from '../store/store.js';
import {ControlStation, Pending} from '../store/const.js';

const factoryApiRws = function (rws) {
  /**
   * The API.RWS namespace is an extension to the Omnicore SDK.
   * @alias API.RWS
   * @namespace
   */
  rws.RWS = new (function () {
    const logModule = 'ecosystem-rws';

    this.MASTERSHIP = {
      Nomaster: 'nomaster',
      Remote: 'remote',
      Local: 'local',
      Internal: 'internal',
    };

    this.LEADTHROUGHSTATUS = {
      Inactive: 'Inactive',
      Active: 'Active',
    };

    function parseJSON(json) {
      try {
        return JSON.parse(json);
      } catch (error) {
        return undefined;
      }
    }

    function rwsPost(url, body, error_msg) {
      return RWS.Network.post(url, body)
        .then((ret) => {
          if (!ret || !ret.response) {
            return Promise.resolve();
          }
          const responseText = ret.response;
          try {
            const content = JSON.parse(responseText);
            return Promise.resolve(content);
          } catch (e) {
            return Promise.resolve(responseText);
          }
        })
        .catch((err) => {
          return API.rejectWithStatus(error_msg, err, {errorCode: ErrorCode.FailedToSendRWSPost});
        });
    }

    async function rwsPostRequest(url, body) {
      const ret = await RWS.Network.post(url, body);
      if (!ret || !ret.response) {
        return;
      }
      const responseText = ret.response;
      try {
        const content = JSON.parse(responseText);
        return content;
      } catch (e) {
        return responseText;
      }
    }

    /**
     * @typedef {'edit' | 'motion' } MastershipType
     * @memberof API.RWS
     */

    /**
     * Requests edit/motion mastership.
     * @alias requestMastership
     * @memberof API.RWS
     * @param {MastershipType} type The mastership type, which can be 'edit' or 'motion'.
     * @note SPOC systems (e.g., RobotWare 8): This function internally calls RWS.Mastership.request() / RWS.MotionMastership.request(), which invokes appSpocWriteAccessRequired to check if write access is held. If write access is not held, an error will be thrown.
     * @note Non-SPOC systems (e.g., RobotWare 7): Edit/motion mastership is requested directly.
     * @returns {Promise<any>}
     * @example
     * API.RWS.requestMastership('edit')
     */
    this.requestMastership = function (type = 'edit') {
      if (type === 'edit')
        return RWS.Mastership.request()
          .then(() => Promise.resolve())
          .catch((err) => {
            return API.rejectWithStatus('Could not get edit Mastership.', err, {
              errorCode: ErrorCode.FailedToRequestEditMastership,
            });
          });
      else if (type == 'motion') {
        return RWS.MotionMastership.request()
          .then(() => Promise.resolve())
          .catch((err) => {
            return API.rejectWithStatus(
              'Could not get motion Mastership.',
              err,
              ErrorCode.FailedToRequestMotionMastership,
              logModule,
            );
          });
      } else if (type != '') type = type + '/';
      return rwsPost(`/rw/mastership/${type}request`, '', `Request ${type} mastership failed.`);
    };

    /**
     * Explicitly request mastership if the system is RW7 and request write access if the system is RW8.
     * If the system is RW7, it can work for both Auto mode and Manual mode.
     * @param {string} type 'edit' | 'motion', only needed by RW7 system
     * @param {boolean} autoRegisterControlStation Whether to automatically register as a remote control station if not registered when requesting write access. Only applicable for RW8 system.
     * @alias requestMastershipExplicitly
     * @memberof API.RWS
     * @returns {Promise<any>}
     */
    this.requestMastershipExplicitly = async function ({type = 'edit', autoRegisterControlStation = false} = {}) {
      if (await API.RWS.CONTROLSTATION.isSpocSystem()) {
        // RW8
        return API.RWS.CONTROLSTATION.requestWriteAccess(autoRegisterControlStation);
      }
      // RW7
      const isTPU = API.isTPU();
      if (isTPU) {
        if (type == 'edit') {
          return RWS.Mastership.request();
        } else if (type == 'motion') {
          return RWS.MotionMastership.request();
        } else {
          throw new Error('Invalid mastership type');
        }
      }
      // Non-TPU
      if (type == 'motion') {
        return RWS.Network.post('/rw/mastership/motion/request');
      } else if (type != 'edit') {
        throw new Error('Invalid mastership type');
      }
      // handle edit mastership for non-FP RW7 system
      const _opmode = await RWS.Controller.getOperationMode();
      const _isInAutoMode = _opmode === API.CONTROLLER.OPMODE.Auto;
      const parsedMsh = await RWS.Mastership.getStatus();
      try {
        if (parsedMsh.mastership == 'nomaster') {
          await RWS.Network.post('/rw/mastership/edit/request'); // cannot call RWS.Mastership.request() because it uses isWebView to check if it is in TPU
        } else if (parsedMsh.mastershipheldbyme && parsedMsh.mastershipheldbyme == 'TRUE') {
          return;
        } else {
          // when non-auto & a local client exists
          if (!_isInAutoMode) {
            try {
              await API.RWS.USER.reqRMMP();
              await API.sleep(1000);
              await API.RWS.USER.pollRMMPState();
            } catch (error) {
              throw new Error(ErrorCode.FailedToRequestRMMP);
            }
            await RWS.Network.post('/rw/mastership/edit/request');
          } else {
            // cannot request access when it is held by others in auto mode
            throw new Error(ErrorCode.EditMastershipHeldByOther);
          }
        }
      } catch (e) {
        if (typeof e.message == 'string') {
          if (e.message === ErrorCode.EditMastershipHeldByOther || e.message === ErrorCode.FailedToRequestRMMP) {
            throw e;
          }
        }
        return new Error(ErrorCode.FailedToRequestEditMastership);
      }
    };

    /**
     * Explicitly release mastership if the system is RW7 and release write access if the system is RW8.
     * If the system is RW7, it can work for both Auto mode and Manual mode.
     * @param {*} type 'edit' | 'motion', only needed by RW7 system
     * @alias releaseMastershipExplicitly
     * @memberof API.RWS
     * @returns {Promise<any>}
     */
    this.releaseMastershipExplicitly = async function (type = 'edit') {
      if (await API.RWS.CONTROLSTATION.isSpocSystem()) {
        // RW8
        return API.RWS.CONTROLSTATION.releaseWriteAccess();
      } else {
        // RW7
        const isTPU = API.isTPU();
        if (isTPU) {
          if (type == 'edit') {
            return RWS.Mastership.release();
          } else if (type == 'motion') {
            return RWS.MotionMastership.release();
          } else {
            throw new Error('Invalid mastership type');
          }
        }
        // Non-TPU
        if (type === 'motion') {
          return RWS.Network.post('/rw/mastership/motion/release');
        } else if (type !== 'edit') {
          throw new Error('Invalid mastership type');
        }
        try {
          const parsedMsh = await RWS.Mastership.getStatus();
          if (parsedMsh.mastershipheldbyme && parsedMsh.mastershipheldbyme == 'TRUE') {
            await RWS.Network.post('/rw/mastership/edit/release');
            await API.RWS.USER.relRMMP();
          }
        } catch (e) {
          return API.rejectWithStatus(
            'Failed to release edit mastership',
            e,
            ErrorCode.FailedToReleaseEditMastership,
            logModule,
          );
        }
      }
    };

    /**
     * @alias releaseMastership
     * @memberof API.RWS
     * @param {MastershipType} type The mastership type, which can be 'edit' or 'motion'.
     * @note SPOC systems (e.g., RobotWare 8): This function internally calls RWS.Mastership.release() / RWS.MotionMastership.release(), which returns directly when in SPOC system.
     * @note Non-SPOC systems (e.g., RobotWare 7): Edit / motion mastership is released directly.
     * @returns {Promise<any>}
     * @example
     * API.RWS.releaseMastership('edit')
     */
    this.releaseMastership = function (type = 'edit') {
      if (type === 'edit')
        return RWS.Mastership.release()
          .then(() => Promise.resolve())
          .catch((err) => {
            return API.rejectWithStatus('Could not release edit Mastership.', err, {
              errorCode: ErrorCode.FailedToReleaseEditMastership,
            });
          });
      else if (type == 'motion') {
        return RWS.MotionMastership.release()
          .then(() => Promise.resolve())
          .catch((err) => {
            return API.rejectWithStatus(
              'Could not release motion Mastership.',
              err,
              ErrorCode.FailedToReleaseMotionMastership,
              logModule,
            );
          });
      } else if (type != '') type = type + '/';
      return rwsPost(`/rw/mastership/${type}release`, '', `Release ${type} mastership failed.`);
    };

    /**
     * Executes the function with holding the specified mastership.
     * @alias executeWithMastership
     * @memberof API.RWS
     * @param {any} args The arguments for function calling.
     * @param {Function} func The function to be called.
     * @param {MastershipType} type The mastership type, which can be 'edit' or 'motion'.
     * @returns {Promise<any>}
     */
    const executeWithMastership = async function (func, args, type = 'edit') {
      let hasMastership = false;

      try {
        await API.RWS.requestMastership(type);
        hasMastership = true;
        await func.apply(API.RWS, args);
      } finally {
        if (hasMastership) {
          await API.RWS.releaseMastership(type);
        }
      }
    };
    /**
     * @alias getMastershipState
     * @note SPOC systems (e.g., RobotWare 8): mastership concept is not supported. Use {@link API.RWS.CONTROLSTATION.checkIfHeldWriteAccess} to check write access status.
     * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface to get edit/motion mastership status.
     * @memberof API.RWS
     * @param {string} type The mastership type, which can be 'edit' or 'motion'.
     * @returns {Promise<object>}
     */
    this.getMastershipState = async function (type = 'edit') {
      try {
        let res = await RWS.Network.get(`/rw/mastership/${type}`);
        let obj = parseJSON(res.responseText);
        return obj['state'][0]['mastership'];
      } catch (err) {
        return API.rejectWithStatus(`Failed to get ${type} mastership status`, err, {
          errorCode: ErrorCode.FailedToGetMastershipStatus,
        });
      }
    };

    /**
     * Checks if held the mastership.
     * @alias checkIfHeldMastership
     * @memberof API.RWS
     * @param {string} type The mastership type, which can be 'edit' or 'motion'.
     * @note SPOC systems (e.g., RobotWare 8): Always returns false. Mastership is not supported. Use {@link API.RWS.CONTROLSTATION.checkIfHeldWriteAccess} instead.
     * @note Non-SPOC systems (e.g., RobotWare 7): Returns true if mastership is held by current client.
     * @returns {Promise<boolean>}
     * @example
     * // check if holding edit mastership
     * await API.RWS.checkIfHeldMastership()
     */
    this.checkIfHeldMastership = async function (type = 'edit') {
      try {
        const isSpoc = await API.RWS.CONTROLSTATION.isSpocSystem();
        if (isSpoc) return false;

        let res = await RWS.Network.get(`/rw/mastership/${type}`);
        let obj = parseJSON(res.responseText);
        let state = obj['state'][0];
        return state && state.mastershipheldbyme && state.mastershipheldbyme == 'TRUE';
      } catch (err) {
        return API.rejectWithStatus(`Failed to check if held ${type} mastership`, err, {
          errorCode: ErrorCode.FailedToCheckIfHeldMastership,
        });
      }
    };

    function parseResponse(res) {
      let items = [];
      let obj = parseJSON(res.responseText);
      if (typeof obj === 'undefined') return Promise.reject('Could not parse JSON.');

      let resources = obj._embedded.resources;
      resources.forEach((item) => {
        let it = {};
        it.name = item._title;
        for (let prop in item) {
          if (prop.indexOf('_') === -1) {
            it[prop] = item[prop];
          }
        }
        items.push(it);
      });
      return items;
    }

    /**
     * This class provides motion-related interfaces that are not supported by the OmniCore SDK.
     * @namespace MOTIONSYSTEM
     * @memberof API.RWS
     */
    this.MOTIONSYSTEM = new (function () {
      /**
       *
       * @typedef Mechunits
       * @prop {string} activationAllowed
       * @prop {string} driveModule
       * @prop {string} mode // 'Activated', ...
       * @prop {string} name
       * @memberof API.RWS.MOTIONSYSTEM
       */

      /**
       *
       * @typedef Mechunit
       * @prop {string} axes
       * @prop {string} axesTotal
       * @prop {string} coords
       * @prop {string} hasIntegratedUnit
       * @prop {string} isIntegratedUnit
       * @prop {string} jogMode
       * @prop {string} mode
       * @prop {string} payload
       * @prop {string} status
       * @prop {string} task
       * @prop {string} tool
       * @prop {string} totalPayload
       * @prop {string} type
       * @prop {string} wobj
       * @prop {string} name
       * @memberof API.RWS.MOTIONSYSTEM
       */

      /**
       * Gets details of all the mechanical units.
       * @alias getMechunits
       * @memberof API.RWS.MOTIONSYSTEM
       * @returns {Promise<Mechunits[]>}
       * @example
       * const mechunits = await API.RWS.MOTIONSYSTEM.getMechunits();
       * console.log(mechunits); // Outputs an array of mechanical units
       */
      this.getMechunits = async function () {
        try {
          let res = await RWS.Network.get('/rw/motionsystem/mechunits');
          let items = [];
          let obj = parseJSON(res.responseText);
          if (typeof obj === 'undefined') return Promise.reject('Could not parse the response of getting mechunits');

          let resources = obj._embedded.resources;
          resources.forEach((item) => {
            let it = {};
            it.name = item._title;
            for (let prop in item) {
              if (prop.indexOf('_') === -1) {
                it[prop] = item[prop];
              }
            }
            items.push(it);
          });
          return items;
        } catch (err) {
          return API.rejectWithStatus('Failed to get mechunits.', err, {errorCode: ErrorCode.FailedToGetMechunits});
        }
      };

      /**
       * Gets the details of a mechanical unit.
       * @alias getMechunit
       * @memberof API.RWS.MOTIONSYSTEM
       * @param {string} name Name of the mechanical unit., e.g., 'ROB_1'
       * @returns {Promise<Mechunit>}
       * @example
       * const mechunit = await API.RWS.MOTIONSYSTEM.getMechunit('ROB_1');
       * console.log(mechunit); // Outputs details of the specified mechanical unit
       */
      this.getMechunit = async function (name = 'ROB_1') {
        try {
          let res = await RWS.Network.get(`/rw/motionsystem/mechunits/${name}?continue-on-err=1`);
          let obj = parseJSON(res.responseText);

          let mechunit = {};

          mechunit.axes = obj.state[0].axes;
          mechunit.axesTotal = obj.state[0]['axes-total'];
          mechunit.coords = obj.state[0]['coord-system'];
          mechunit.hasIntegratedUnit = obj.state[0]['has-integrated-unit'];
          mechunit.isIntegratedUnit = obj.state[0]['is-integrated-unit'];
          mechunit.jogMode = obj.state[0]['jog-mode'];
          mechunit.mode = obj.state[0]['mode'];
          mechunit.payload = obj.state[0]['payload-name'];
          mechunit.status = obj.state[0]['status'];
          mechunit.task = obj.state[0]['task-name'];
          mechunit.tool = obj.state[0]['tool-name'];
          mechunit.totalPayload = obj.state[0]['total-payload-name'];
          mechunit.type = obj.state[0]['type'];
          mechunit.wobj = obj.state[0]['wobj-name'];
          mechunit.name = obj.state[0]._title;

          return mechunit;
        } catch (err) {
          return API.rejectWithStatus(`Failed to get mechunit ${name} info`, err, {
            errorCode: ErrorCode.FailedToGetMechunitInfo,
          });
        }
      };

      /**
       * @typedef SetMechunitProps
       * @prop {string} [name]
       * @prop {string} [tool]
       * @prop {string} [wobj]
       * @prop {string} [coords]
       * @prop {string} [jogMode]
       * @prop {string} [mode]
       * @prop {string} [payload]
       * @prop {string} [totalPayload]
       * @memberof API.RWS.MOTIONSYSTEM
       */

      /**
       * Sets the properties of a mechanical unit.
       * @alias setMechunit
       * @memberof API.RWS.MOTIONSYSTEM
       * @param {SetMechunitProps} props See {@link SetMechunitProps}
       * @returns {Promise<any>}
       * @example
       * await API.RWS.MOTIONSYSTEM.setMechunit({
       *   name: 'ROB_1',
       *   tool: 'Tool1',
       *   wobj: 'WObj1',
       * });
       */
      this.setMechunit = async function ({
        name = 'ROB_1',
        tool = '',
        wobj = '',
        coords = '',
        payload = '',
        totalPayload = '',
        mode = '',
        jogMode = '',
      } = {}) {
        let url = `/rw/motionsystem/mechunits/${name}?continue-on-err=1`;
        let body = '';
        body += tool ? 'tool=' + tool : '';
        body += wobj ? (body ? '&' : '') + 'wobj=' + wobj : '';
        body += payload ? (body ? '&' : '') + 'payload=' + payload : '';
        body += totalPayload ? (body ? '&' : '') + 'total-payload=' + totalPayload : '';
        body += mode ? (body ? '&' : '') + 'mode=' + mode : '';
        body += jogMode ? (body ? '&' : '') + 'jog-mode=' + jogMode : '';
        body += coords ? (body ? '&' : '') + 'coord-system=' + coords : '';

        let res = await rwsPost(url, body, 'Failed to set mechunit.');
        return res;
      };

      /**
       * Sets the robot position target for GoTo task.
       * @alias setRobotPositionTarget
       * @memberof API.RWS.MOTIONSYSTEM
       * @param {API.MOTION.RobTarget} r See {@link API.MOTION.RobTarget}
       * @returns {Promise<any>}
       * @example
       * const robTarget = {
       *   trans: { x: 100, y: 200, z: 300 },
       *   rot: { q1: 1, q2: 0, q3: 0, q4: 0 },
       *   robconf: { cf1: 0, cf4: 0, cf6: 0, cfx: 0 },
       *   extax: { eax_a: 0, eax_b: 0, eax_c: 0, eax_d: 0, eax_e: 0, eax_f: 0 },
       * };
       * await API.RWS.MOTIONSYSTEM.setRobotPositionTarget(robTarget);
       */
      this.setRobotPositionTarget = function (r) {
        if (r.trans === undefined || r.rot === undefined || r.robconf === undefined) {
          return API.rejectWithStatus("Parameter 'r' is not a robtarget.", {}, {errorCode: ErrorCode.InvalidPrameter});
        }

        let url = `/rw/motionsystem/position-target`;
        let body = `pos-x=${r.trans.x}&pos-y=${r.trans.y}&pos-z=${r.trans.z}&orient-q1=${r.rot.q1}&orient-q2=${
          r.rot.q2
        }&orient-q3=${r.rot.q3}&orient-q4=${r.rot.q4}&config-j1=${r.robconf.cf1}&config-j4=${r.robconf.cf4}&config-j6=${
          r.robconf.cf6
        }&config-jx=${r.robconf.cfx}&extjoint-1=${r.extax ? r.extax.eax_a : 9e9}&extjoint-2=${
          r.extax ? r.extax.eax_b : 9e9
        }&extjoint-3=${r.extax ? r.extax.eax_c : 9e9}&extjoint-4=${r.extax ? r.extax.eax_d : 9e9}&extjoint-5=${
          r.extax ? r.extax.eax_e : 9e9
        }&extjoint-6=${r.extax ? r.extax.eax_f : 9e9}`;

        return rwsPost(url, body, 'Failed to set target robot position for GoTo');
      };

      /**
       * Sets the joint position target for GoTo task.
       * @alias setRobotPositionJoint
       * @memberof API.RWS.MOTIONSYSTEM
       * @param {API.MOTION.JointTarget} j
       * @returns {Promise<any>}
       * @example
       * const jointTarget = {
       *   robax: { rax_1: 0, rax_2: 0, rax_3: 0, rax_4: 0, rax_5: 0, rax_6: 0 },
       *   extax: { eax_a: 0, eax_b: 0, eax_c: 0, eax_d: 0, eax_e: 0, eax_f: 0 },
       * };
       * await API.RWS.MOTIONSYSTEM.setRobotPositionJoint(jointTarget);
       */
      this.setRobotPositionJoint = function (j) {
        if (j.robax === undefined) return Promise.reject("Parameter 'j' is not a jointtarget.");

        let url = `/rw/motionsystem/position-joint`;
        let body = `robjoint=${j.robax.rax_1},${j.robax.rax_2},${j.robax.rax_3},${j.robax.rax_4},${j.robax.rax_5},${
          j.robax.rax_6
        }&extjoint=${j.extax ? j.extax.eax_a : 9e9},${j.extax ? j.extax.eax_b : 9e9},${j.extax ? j.extax.eax_c : 9e9},${
          j.extax ? j.extax.eax_d : 9e9
        },${j.extax ? j.extax.eax_e : 9e9},${j.extax ? j.extax.eax_f : 9e9}`;

        return rwsPost(url, body, 'Failed to set target joint position for GoTo');
      };

      /**
       * Starts jogging. This request shall be sent cyclically during the jogging.
       * @alias jog
       * @memberof API.RWS.MOTIONSYSTEM
       * @param {API.MOTION.JogData} jogdata Jogging speed for the axis.
       * @param {number} ccount Counter value, which needs to be obtained using {@link getChangeCount} before start jogging.
       * @returns {Promise<any>}
       * @example
       * const jogData = [10, 0, 0, 0, 0, 0]; // Jogging along axis 1
       * const changeCount = await API.RWS.MOTIONSYSTEM.getChangeCount();
       * await API.RWS.MOTIONSYSTEM.jog(jogData, changeCount);
       */
      this.jog = async function (jogdata, ccount) {
        let url = `/rw/motionsystem/jog`;
        let body = `axis1=${jogdata[0]}&axis2=${jogdata[1]}&axis3=${jogdata[2]}&axis4=${jogdata[3]}&axis5=${jogdata[4]}&axis6=${jogdata[5]}&ccount=${ccount}`;

        return rwsPost(url, body, `Failed to send jog commands.jogdata: ${jogdata.toString()}. ccount:${ccount}.`);
      };
      /**
       * Gets the change count of the motion system.
       * @alias getChangeCount
       * @memberof API.RWS.MOTIONSYSTEM
       * @returns {Promise<number>}
       * @example
       * const changeCount = await API.RWS.MOTIONSYSTEM.getChangeCount();
       * console.log(`Change count: ${changeCount}`);
       */
      this.getChangeCount = async function () {
        try {
          let res = await RWS.Network.get(`/rw/motionsystem?resource=change-count`);
          let obj = parseJSON(res.responseText);
          return obj['state'][0]['change-count'];
        } catch (err) {
          return API.rejectWithStatus('Failed to get change count of motion system', err, {
            errorCode: ErrorCode.FailedToGetMotionSystemChangeCount,
          });
        }
      };

      this.parseRobTarget = function (robTarget) {
        let rt = {trans: {}, rot: {}, robconf: {}, extax: {}};
        rt.trans.x = parseFloat(robTarget['x']);
        rt.trans.y = parseFloat(robTarget['y']);
        rt.trans.z = parseFloat(robTarget['z']);
        rt.rot.q1 = parseFloat(robTarget['q1']);
        rt.rot.q2 = parseFloat(robTarget['q2']);
        rt.rot.q3 = parseFloat(robTarget['q3']);
        rt.rot.q4 = parseFloat(robTarget['q4']);
        rt.robconf.cf1 = parseFloat(robTarget['cf1']);
        rt.robconf.cf4 = parseFloat(robTarget['cf4']);
        rt.robconf.cf6 = parseFloat(robTarget['cf6']);
        rt.robconf.cfx = parseFloat(robTarget['cfx']);
        rt.extax.eax_a = parseFloat(robTarget['eax_a']);
        rt.extax.eax_b = parseFloat(robTarget['eax_b']);
        rt.extax.eax_c = parseFloat(robTarget['eax_c']);
        rt.extax.eax_d = parseFloat(robTarget['eax_d']);
        rt.extax.eax_e = parseFloat(robTarget['eax_e']);
        rt.extax.eax_f = parseFloat(robTarget['eax_f']);

        return rt;
      };

      /**
       * @typedef RobTargetProps
       * @param {string} [tool]
       * @param {string} [wobj]
       * @param {string} [coords]
       * @param {string} [mechunit]
       * @memberof API.RWS.MOTIONSYSTEM
       */

      /**
       * Gets the current robot position.
       * @alias getRobTarget
       * @memberof API.RWS.MOTIONSYSTEM
       * @param {RobTargetProps} props See {@link RobTargetProps}
       * @returns {Promise<API.MOTION.RobTarget>}
       * @example
       * const robTarget = await API.RWS.MOTIONSYSTEM.getRobTarget('Tool1', 'WObj1');
       */
      this.getRobTarget = async function (tool = '', wobj = '', coords = '', mechunit = 'ROB_1') {
        try {
          let params = '';
          params += tool ? '?tool=' + tool : '';
          params += wobj ? (params ? '&' : '?') + 'wobj=' + wobj : '';
          params += coords ? (params ? '&' : '?') + 'coordinate=' + coords : '';

          let res = await RWS.Network.get(`/rw/motionsystem/mechunits/${mechunit}/robtarget${params}`);

          let obj = parseJSON(res.responseText);

          let rt = this.parseRobTarget(obj['state'][0]);

          return rt;
        } catch (e) {
          return API.rejectWithStatus('Failed to get current robot position', e, {
            errorCode: ErrorCode.FailedToGetRobotTarget,
          });
        }
      };

      this.parseJointTarget = function (jointTarget, toDegrees = false) {
        let jt = {robax: {}, extax: {}};
        let factor = 180 / Math.PI;
        jt.robax.rax_1 = toDegrees ? parseFloat(jointTarget['rax_1']) * factor : parseFloat(jointTarget['rax_1']);
        jt.robax.rax_2 = toDegrees ? parseFloat(jointTarget['rax_2']) * factor : parseFloat(jointTarget['rax_2']);
        jt.robax.rax_3 = toDegrees ? parseFloat(jointTarget['rax_3']) * factor : parseFloat(jointTarget['rax_3']);
        jt.robax.rax_4 = toDegrees ? parseFloat(jointTarget['rax_4']) * factor : parseFloat(jointTarget['rax_4']);
        jt.robax.rax_5 = toDegrees ? parseFloat(jointTarget['rax_5']) * factor : parseFloat(jointTarget['rax_5']);
        jt.robax.rax_6 = toDegrees ? parseFloat(jointTarget['rax_6']) * factor : parseFloat(jointTarget['rax_6']);
        jt.extax.eax_a = parseFloat(jointTarget['eax_a']);
        jt.extax.eax_b = parseFloat(jointTarget['eax_b']);
        jt.extax.eax_c = parseFloat(jointTarget['eax_c']);
        jt.extax.eax_d = parseFloat(jointTarget['eax_d']);
        jt.extax.eax_e = parseFloat(jointTarget['eax_e']);
        jt.extax.eax_f = parseFloat(jointTarget['eax_f']);
        return jt;
      };

      /**
       * Gets the current joint position.
       * @alias getJointTarget
       * @memberof API.RWS.MOTIONSYSTEM
       * @param {string} mechunit
       * @returns {Promise<API.MOTION.JointTarget>}
       * @example
       * const jointTarget = await API.RWS.MOTIONSYSTEM.getJointTarget('ROB_1');
       * console.log(jointTarget); // Outputs the current joint positions
       */
      this.getJointTarget = async function (mechunit = 'ROB_1') {
        try {
          let res = await RWS.Network.get(`/rw/motionsystem/mechunits/${mechunit}/jointtarget`);
          let obj = parseJSON(res.responseText);

          let jt = this.parseJointTarget(obj['state'][0], false);

          return jt;
        } catch (e) {
          return API.rejectWithStatus('Failed to get current robot joint position', e, {
            errorCode: ErrorCode.FailedToGetJointTarget,
          });
        }
      };

      /**
       *
       * @typedef JointsSolutionProps
       * @prop {string} [mechUnit]
       * @prop {API.MOTION.RobTarget} robTarget
       * @prop {API.MOTION.ToolData} [toolData]
       */

      /**
       * Get all joint solutions
       * @alias getAllJointsSolution
       * @memberof API.RWS.MOTIONSYSTEM
       * @param {JointsSolutionProps} props
       * @returns {Promise<any>}
       */
      this.getAllJointsSolution = async function ({mechUnit = 'ROB_1', robTarget, toolData}) {
        /*
         * {
         *   curr_position*	string
         *   [x,y,z]
         *
         *   curr_ext_joints*	string
         *   [j1,j2,j3,j4,j5,j6]
         *
         *   tool_frame_position*	string
         *   [x, y, z]
         *
         *   curr_orientation*	string
         *   [u0, u1, u2, u3]
         *
         *   tool_frame_orientation*	string
         *   [u0, u1, u2, u3]
         *
         *   robot_fixed_object*	string
         *   TRUE|FALSE
         *
         *   robot_configuration*	string
         *   [quarter_rev_j1, quarter_rev_j4, quarter_rev_j6, quarter_rev_jx]
         *
         *   }
         */
        Logger.i(InfoType.RobotOperation, 'Trying to get joint solution for a target position');
        const r = robTarget;
        const t = toolData
          ? toolData
          : {robhold: true, tframe: {trans: {x: 0, y: 0, z: 0}, rot: {q1: 1, q2: 0, q3: 0, q4: 0}}};
        const toMeters = 0.001;

        if (r.trans === undefined || r.rot === undefined || r.robconf === undefined)
          return API.rejectWithStatus(
            "Parameter 'robTarget' is not a valid robtarget value.",
            {},
            {errorCode: ErrorCode.InvalidPrameter},
          );

        let url = `/rw/motionsystem/mechunits/${mechUnit}/all-joints-solution`;
        let body = `curr_position=[${r.trans.x * toMeters},${r.trans.y * toMeters},${
          r.trans.z * toMeters
        }]&curr_ext_joints=[${r.extax ? r.extax.eax_a : 9e9},${r.extax ? r.extax.eax_b : 9e9},${
          r.extax ? r.extax.eax_c : 9e9
        },${r.extax ? r.extax.eax_d : 9e9},${r.extax ? r.extax.eax_e : 9e9},${
          r.extax ? r.extax.eax_f : 9e9
        }]&tool_frame_position=[${t.tframe.trans.x * toMeters},${t.tframe.trans.y * toMeters},${
          t.tframe.trans.z * toMeters
        }]&curr_orientation=[${r.rot.q1},${r.rot.q2},${r.rot.q3},${r.rot.q4}]&tool_frame_orientation=[${
          t.tframe.rot.q1
        },${t.tframe.rot.q2},${t.tframe.rot.q3},${t.tframe.rot.q4}]&robot_fixed_object=${
          t.robhold ? 'TRUE' : 'FALSE'
        }&robot_configuration=[${r.robconf.cf1},${r.robconf.cf4},${r.robconf.cf6},${r.robconf.cfx}]`;

        let res = await rwsPost(url, body, 'Failed to get all joint solutions from Cartesian');

        let toDegree = 180 / Math.PI;
        let jtArray = res.state.map((jointData) => {
          let jt = {robax: {}, extax: {}, robconf: {}};

          jt.robax.rax_1 = parseFloat(jointData['robotjoint1']) * toDegree;
          jt.robax.rax_2 = parseFloat(jointData['robotjoint2']) * toDegree;
          jt.robax.rax_3 = parseFloat(jointData['robotjoint3']) * toDegree;
          jt.robax.rax_4 = parseFloat(jointData['robotjoint4']) * toDegree;
          jt.robax.rax_5 = parseFloat(jointData['robotjoint5']) * toDegree;
          jt.robax.rax_6 = parseFloat(jointData['robotjoint6']) * toDegree;
          jt.extax.eax_a = parseFloat(jointData['extjoint1']);
          jt.extax.eax_b = parseFloat(jointData['extjoint2']);
          jt.extax.eax_c = parseFloat(jointData['extjoint3']);
          jt.extax.eax_d = parseFloat(jointData['extjoint4']);
          jt.extax.eax_e = parseFloat(jointData['extjoint5']);
          jt.extax.eax_f = parseFloat(jointData['extjoint6']);
          jt.robconf.cf1 = parseInt(jointData['quarter_rev_j11']);
          jt.robconf.cf4 = parseInt(jointData['quarter_rev_j4']);
          jt.robconf.cf6 = parseInt(jointData['quarter_rev_j6']);
          jt.robconf.cfx = parseInt(jointData['quarter_rev_jx']);

          return jt;
        });

        return jtArray;
      };

      /**
       *
       * @typedef JontsFromCartesianProps
       * @prop {string} [mechUnit]
       * @prop {API.MOTION.RobTarget} robTarget
       * @prop {API.MOTION.ToolData} [toolData]
       * @prop {API.MOTION.JointTarget} jointTarget
       */

      /**
       * Get all the joint solutions.
       * @alias getJointsFromCartesian
       * @memberof API.RWS.MOTIONSYSTEM
       * @param {JontsFromCartesianProps} props
       * @returns {Promise<any>}
       */
      this.getJointsFromCartesian = async function ({mechUnit = 'ROB_1', robTarget, toolData, jointTarget}) {
        /*
         * {
         *    curr_position*	string
         *    [x,y,z]
         *
         *    curr_ext_joints*	string
         *    [j1,j2,j3,j4,j5,j6]
         *
         *    tool_frame_position*	string
         *    [x, y, z]
         *
         *    curr_orientation*	string
         *    [u0, u1, u2, u3]
         *
         *    tool_frame_orientation*	string
         *    [u0, u1, u2, u3]
         *
         *    old_rob_joints*	string
         *    [j1,j2,j3,j4,j5,j6]
         *
         *    old_ext_joints*	string
         *    [j1,j2,j3,j4,j5,j6]
         *
         *    robot_fixed_object*	string
         *    TRUE|FALSE
         *
         *    robot_configuration*	string
         *    [quarter_rev_j1, quarter_rev_j4, quarter_rev_j6, quarter_rev_jx]
         *
         *    elog_at_error*	string
         *    TRUE|FALSE
         *
         *  }
         */
        Logger.i(InfoType.RobotOperation, 'Trying to get joint solution for a target position');
        const r = robTarget;
        const j = jointTarget;
        const t = toolData
          ? toolData
          : {robhold: true, tframe: {trans: {x: 0, y: 0, z: 0}, rot: {q1: 1, q2: 0, q3: 0, q4: 0}}};

        const toRadians = Math.PI / 180;

        const toMeters = 0.001;

        if (r.trans === undefined || r.rot === undefined || r.robconf === undefined || j.robax === undefined)
          return API.rejectWithStatus(
            "Parameter 'robTarget' is not a valid robtarget value.",
            {},
            {errorCode: ErrorCode.InvalidPrameter},
          );

        let url = `/rw/motionsystem/mechunits/${mechUnit}/joints-from-cartesian`;
        let body = `curr_position=[${r.trans.x * toMeters},${r.trans.y * toMeters},${
          r.trans.z * toMeters
        }]&curr_ext_joints=[${r.extax ? r.extax.eax_a : 9e9},${r.extax ? r.extax.eax_b : 9e9},${
          r.extax ? r.extax.eax_c : 9e9
        },${r.extax ? r.extax.eax_d : 9e9},${r.extax ? r.extax.eax_e : 9e9},${
          r.extax ? r.extax.eax_f : 9e9
        }]&tool_frame_position=[${t.tframe.trans.x},${t.tframe.trans.y},${t.tframe.trans.z}]&curr_orientation=[${
          r.rot.q1
        },${r.rot.q2},${r.rot.q3},${r.rot.q4}]&tool_frame_orientation=[${t.tframe.rot.q1},${t.tframe.rot.q2},${
          t.tframe.rot.q3
        },${t.tframe.rot.q4}]&old_rob_joints=[${j.robax.rax_1 * toRadians},${j.robax.rax_2 * toRadians},${
          j.robax.rax_3 * toRadians
        },${j.robax.rax_4 * toRadians},${j.robax.rax_5 * toRadians},${j.robax.rax_6 * toRadians}]&old_ext_joints=[${
          j.extax ? j.extax.eax_a : 9e9
        },${j.extax ? j.extax.eax_b : 9e9},${j.extax ? j.extax.eax_c : 9e9},${j.extax ? j.extax.eax_d : 9e9},${
          j.extax ? j.extax.eax_e : 9e9
        },${j.extax ? j.extax.eax_f : 9e9}]&robot_fixed_object=${t.robhold ? 'TRUE' : 'FALSE'}&robot_configuration=[${
          r.robconf.cf1
        },${r.robconf.cf4},${r.robconf.cf6},${r.robconf.cfx}]&elog_at_error=TRUE`;

        let res = await rwsPost(url, body, 'Failed to get joint solution from Cartesian');

        let jt = {robax: {}, extax: {}};
        let toDegree = 180 / Math.PI;

        jt.robax.rax_1 = parseFloat(res.state[0]['robotjoint1']) * toDegree;
        jt.robax.rax_2 = parseFloat(res.state[0]['robotjoint2']) * toDegree;
        jt.robax.rax_3 = parseFloat(res.state[0]['robotjoint3']) * toDegree;
        jt.robax.rax_4 = parseFloat(res.state[0]['robotjoint4']) * toDegree;
        jt.robax.rax_5 = parseFloat(res.state[0]['robotjoint5']) * toDegree;
        jt.robax.rax_6 = parseFloat(res.state[0]['robotjoint6']) * toDegree;
        jt.extax.eax_a = parseFloat(res.state[0]['extjoint1']);
        jt.extax.eax_b = parseFloat(res.state[0]['extjoint2']);
        jt.extax.eax_c = parseFloat(res.state[0]['extjoint3']);
        jt.extax.eax_d = parseFloat(res.state[0]['extjoint4']);
        jt.extax.eax_e = parseFloat(res.state[0]['extjoint5']);
        jt.extax.eax_f = parseFloat(res.state[0]['extjoint6']);
        return jt;
      };

      /**
       * Gets the lead-through status.
       * @alias getLeadThroughStatus
       * @memberof API.RWS.MOTIONSYSTEM
       * @param {string} mechunit
       * @returns {Promise<any>}
       * @example
       * const status = await API.RWS.MOTIONSYSTEM.getLeadThroughStatus('ROB_1');
       * console.log(`Lead-through status: ${status}`);
       */
      this.getLeadThroughStatus = async function (mechunit = 'ROB_1') {
        try {
          let ltRes = await RWS.Network.get(`/rw/motionsystem/mechunits/${mechunit}/lead-through`);
          let obj = parseJSON(ltRes.responseText);
          return obj['state'][0]['status'];
        } catch (e) {
          return API.rejectWithStatus('Get leadthrough status failed.', e, {
            errorCode: ErrorCode.FailedToGetLeadthroughStatus,
          });
        }
      };
      /**
       * Sets the lead-through status.
       * @alias setLeadThroughStatus
       * @memberof API.RWS.MOTIONSYSTEM
       * @param {string} type API.RWS.LEADTHROUGHSTATUS.Inactive | API.RWS.LEADTHROUGHSTATUS.Active
       * @param {string} disableAutoLoadCompensation Being Active if the precision mode is used. This is supported from RW7.6.
       * @param {string} mechunit The name of the mechanical unit.
       * @returns {Promise<any>}
       * @example
       * await API.RWS.MOTIONSYSTEM.setLeadThroughStatus(
       *   API.RWS.LEADTHROUGHSTATUS.Active,
       *   'inactive',
       *   'ROB_1'
       * );
       * console.log('Lead-through status set successfully.');
       */
      this.setLeadThroughStatus = async function (type, disableAutoLoadCompensation = 'inactive', mechunit = 'ROB_1') {
        let data = 'status=' + type;
        let systemInfo = await API.RWS.CONTROLLER.getSystemInfo();
        if (parseInt(systemInfo['major']) == 7 && parseInt(systemInfo['minor']) >= 6) {
          data += '&disableautoloadcompensation=' + disableAutoLoadCompensation;
        }
        try {
          await RWS.Network.post('/rw/motionsystem/mechunits/' + mechunit + '/lead-through', data);
        } catch (e) {
          return API.rejectWithStatus(`Failed to set leadthrough status to ${type}`, e, {
            errorCode: ErrorCode.FailedToSetLeadthrough,
          });
        }
      };
    })();

    /**
     * The API.RWS.RAPID namespace provides RAPID-related interfaces that are not supported by the OmniCore SDK.
     * @namespace RAPID
     * @memberof API.RWS
     */
    this.RAPID = new (function () {
      /**
       * Loads a module from the HOME directory of the controller into the specified task.
       * @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.
       * @alias loadModule
       * @memberof API.RWS.RAPID
       * @param {string} path Path to the module file in the HOME directory (including the file extension).
       * @param {boolean} [replace] If the value is 'true', existing module in RAPID with the same name will be replaced.
       * @param {string} [taskName] The task name.
       * @param {boolean} [requestMsh] Edit mastership needs to be requested before loading the module.
       * @returns {Promise<any>}
       * @example
       * let url = `${this.path}/${this.name}${this.extension}`;
       * await API.RWS.RAPID.loadModule(url, true, 'T_ROB1');
       */
      this.loadModule = async function (path, replace = false, taskName = 'T_ROB1', requestMsh = true) {
        const f = async function () {
          await RWS.Network.post(`/rw/rapid/tasks/${taskName}/loadmod`, 'modulepath=' + path + '&replace=' + replace);
        };
        try {
          if (requestMsh) {
            return await executeWithMastership(f);
          } else {
            return await f();
          }
        } catch (e) {
          return API.rejectWithStatus(`Failed to load RAPID module at path ${path}`, e, {
            errorCode: ErrorCode.FailedToLoadRapidModule,
          });
        }
      };

      /**
       * Unloads a RAPID module.
       * @alias unloadModule
       * @memberof API.RWS.RAPID
       * @note SPOC systems (e.g., RobotWare 8): Write access must be held before unloading module. 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} [taskName] The task name.
       * @param {boolean} [requestMsh] Edit mastership needs to be requested before unloading the module.
       * @returns {Promise<any>}
       * @example
       * await API.RWS.RAPID.unloadModule('MyModule', 'T_ROB1');
       */
      this.unloadModule = async function (moduleName, taskName = 'T_ROB1', requestMsh = true) {
        const f = function () {
          return RWS.Network.post(`/rw/rapid/tasks/${taskName}/unloadmod`, 'module=' + moduleName);
        };
        try {
          if (requestMsh) {
            return await executeWithMastership(f);
          } else {
            return await f();
          }
        } catch (e) {
          return API.rejectWithStatus(`Failed to unload RAPID module. Task: ${taskName}. Module: ${moduleName}`, e, {
            errorCode: ErrorCode.FailedToUnloadRapidModule,
          });
        }
      };

      /**
       * Gets the RAPID module text.
       * @alias getModuleText
       * @memberof API.RWS.RAPID
       * @param {*} moduleName The module name.
       * @param {*} taskName The task name.
       * @returns {Promise<any>} An object containing the structure
       * {
       *  change-count,
       *  module-length,
       *  module-text?,
       *  file-path?,
       *  _title,
       *  _type,
       * }
       * @example
       * const moduleText = await API.RWS.RAPID.getModuleText('MyModule', 'T_ROB1');
       */
      this.getModuleText = async function (moduleName, taskName = 'T_ROB1') {
        try {
          let res = await RWS.Network.get(`/rw/rapid/tasks/${taskName}/modules/${moduleName}/text`);
          var fixedResponse = res.responseText.replace(/""(.*?)""/g, '"$1"');
          let obj = parseJSON(fixedResponse);

          if (typeof obj === 'undefined') return Promise.reject('Could not parse JSON.');
          if (!obj.state[0]) return Promise.reject('Failed to get module text.');

          return obj.state[0];
        } catch (e) {
          return API.rejectWithStatus(
            `Failed to get module text content. Task: ${taskName}. Module: ${moduleName}`,
            e,
            {errorCode: ErrorCode.FailedToGetRapidModuleContent},
          );
        }
      };

      /**
       * Sets the content of a RAPID module.
       * @alias setModuleText
       * @memberof API.RWS.RAPID
       * @param {string} moduleName The module name.
       * @param {string} text The new content of the module.
       * @param {string} [taskName] The task name.
       * @returns {Promise<any>}
       * @example
       * await API.RWS.RAPID.setModuleText("Module1",`MODULE Module1\r\nPROC proc1()\r\nTPWrite "123";\r\nENDPROC\r\nENDMODULE`), 'T_ROB1');
       */
      this.setModuleText = async function (moduleName, text, taskName = 'T_ROB1') {
        const url = `/rw/rapid/tasks/${taskName}/modules/${moduleName}/text`;
        const body = `text=${encodeURIComponent(text)}`;
        return await rwsPost(url, body, `Failed to set content of ${taskName}:${moduleName}.`);
      };

      /**
       * Sets the content of a RAPID module within a specified range.
       * @alias setModuleTextInRange
       * @memberof API.RWS.RAPID
       * @param {string} moduleName The module name.
       * @param {string} text The new content to insert.
       * @param {number} startRow Start row of the range.
       * @param {number} startCol Start column of the range.
       * @param {number} endRow End row of the range.
       * @param {number} endCol End column of the range.
       * @param {string} [taskName] The task name.
       * @param {string} [replaceMode] Replace mode, e.g., 'Replace'.
       * @returns {Promise<any>}
       * @example
       * await API.RWS.RAPID.setModuleTextInRange(
       *   'MyModule',
       *   'TPErase;',
       *   5,
       *   1,
       *   10,
       *   1,
       *   'T_ROB1',
       *   'Replace'
       * );
       */
      this.setModuleTextInRange = async function (
        moduleName,
        text,
        startRow,
        startCol,
        endRow,
        endCol,
        taskName = 'T_ROB1',
        replaceMode = 'Replace',
      ) {
        const url = `/rw/rapid/tasks/${taskName}/modules/${moduleName}/text/range`;
        const body = `replace-mode=${replaceMode}&query-mode=Force&startrow=${startRow}&startcol=${startCol}&endrow=${endRow}&endcol=${endCol}&text=${encodeURIComponent(
          text,
        )}`;
        return rwsPost(url, body, `Failed to set content range of ${taskName}:${moduleName}.`);
      };

      /**
       * Moves the program pointer to a specified cursor position.
       * @alias movePPToCursor
       * @memberof API.RWS.RAPID
       * @param {string} moduleName The module name.
       * @param {string} taskName The task name.
       * @param {string} line Number of the line to which the pointer is desired.
       * @param {string} column Number of the column to which the pointer is desired.
       * @returns {Promise<any>}
       * @example
       * await API.RWS.RAPID.movePPToCursor('MyModule', 'T_ROB1', '10', '5');
       */
      this.movePPToCursor = function (moduleName, taskName, line, column) {
        if (typeof moduleName !== 'string') return Promise.reject("Parameter 'module' is not a string.");
        if (typeof line !== 'string') return Promise.reject("Parameter 'line' is not a string.");
        if (typeof column !== 'string') return Promise.reject("Parameter 'column' is not a string.");

        let url = `/rw/rapid/tasks/${encodeURIComponent(taskName)}/pcp/cursor?mastership=implicit`;
        let body = `module=${encodeURIComponent(moduleName)}&line=${encodeURIComponent(
          line,
        )}&column=${encodeURIComponent(column)}`;
        return rwsPost(
          url,
          body,
          `Failed to set PP to cursor. Task: ${taskName}. Module: ${moduleName}. line:${line}. column: ${column}.`,
        );
      };

      /**
       * Sets the program pointer to a specified routine.
       * @alias setPPToRoutine
       * @memberof API.RWS.RAPID
       * @param {string} moduleName The module name.
       * @param {string} routineName Routine's name.
       * @param {string} [taskName] The task name.
       * @returns {Promise<any>}
       * @example
       * await API.RWS.RAPID.setPPToRoutine('MyModule', 'MainRoutine', 'T_ROB1');
       */
      this.setPPToRoutine = async function (moduleName, routineName, taskName = 'T_ROB1') {
        let url = `/rw/rapid/tasks/${taskName}/pcp/routine-from-url`;
        let body = `routineurl=/RAPID/${taskName}/${moduleName}/${routineName}&userlevel=false`;
        try {
          return await rwsPostRequest(url, body);
        } catch (error) {
          return API.rejectWithStatus(
            `Failed to set PP to routine. Task: ${taskName}. Module: ${moduleName}. Routine: ${routineName}.`,
            error,
            {
              errorCode: ErrorCode.FailedToSetPPToRoutine,
            },
          );
        }
      };

      /**
       * Gets the information of program pointers
       * @alias getPointersInfo
       * @memberof API.RWS.RAPID
       * @param {string} [taskName] The task name.
       * @returns {Promise<any>}
       * @example
       * const pointersInfo = await API.RWS.RAPID.getPointersInfo('T_ROB1');
       */
      this.getPointersInfo = async function (taskName = 'T_ROB1') {
        try {
          let res = await RWS.Network.get('/rw/rapid/tasks/' + taskName + '/pcp');
          let pcpInfo = JSON.parse(res.responseText)['state'];
          return pcpInfo;
        } catch (e) {
          return API.rejectWithStatus(`Failed to get program/motion pointers information.  Task: ${taskName}.`, e, {
            errorCode: ErrorCode.FailedToGetPointersInfo,
          });
        }
      };

      /**
       * Synchronizes the value of persistent variables in a specified module.
       * @alias syncPers
       * @memberof API.RWS.RAPID
       * @param {string} moduleName The module name.
       * @param {string} [taskName] The task name.
       * @note SPOC systems (e.g., RobotWare 8): This function requires write access. An error will be thrown if write access is not held.
       * @note Non-SPOC systems (e.g., RobotWare 7): Use {@link API.RWS.requestMastership} to request edit mastership before calling this API.
       * @returns {Promise<any>}
       * @example
       * await API.RWS.RAPID.syncPers('MyModule', 'T_ROB1');
       */
      this.syncPers = async function (moduleName, taskName = 'T_ROB1') {
        try {
          let url = `/rw/rapid/tasks/${taskName}/modules/${moduleName}/sync-pers`;
          return await rwsPostRequest(url, '');
        } catch (e) {
          if (e && e.controllerStatus && e.controllerStatus.code === '-1073442809') {
            return API.rejectWithStatus(`Failed to sync pers. Task: ${taskName}. Module: ${moduleName}`, e, {
              errorCode: ErrorCode.FailedToSyncPersDuringExecution,
            });
          } else {
            throw API.rejectWithStatus(`Failed to sync pers. Task: ${taskName}. Module: ${moduleName}`, e, {
              errorCode: ErrorCode.FailedToSyncPers,
            });
          }
        }
      };
      /**
       * Gets the properties of the specified RAPID data type
       * @alias getDataTypeProperty
       * @memberof API.RWS.RAPID
       * @param {string} typeUrl The URL of the data type.
       * @returns {Promise<any>}
       * @example
       * await API.RWS.RAPID.getDataTypeProperty('robtarget');
       */
      this.getDataTypeProperty = async function (typeUrl) {
        try {
          let res = await RWS.Network.get(`/rw/rapid/symbol/${typeUrl}/properties`);
          let typeProp = JSON.parse(res.responseText)['state'][0];
          return typeProp;
        } catch (e) {
          return API.rejectWithStatus(`Failed to get data type property of ${typeUrl}`, e, {
            errorCode: ErrorCode.FailedToGetDataTypeProperty,
          });
        }
      };

      /**
       * Gets the RECORD data list.
       * @alias getRecordTypes
       * @memberof API.RWS.RAPID
       * @param {string} task The task name.
       * @returns {Promise<any>}
       * @example
       * await API.RWS.RAPID.getRecordTypes()
       */
      this.getRecordTypes = function (task = 'T_ROB1', url = 'symbols/search') {
        let data = '';
        data += '&blockurl=' + `RAPID/${task}`;
        data += '&recursive=TRUE'; // True all the way
        data += '&symtyp=rec';
        data += "&regexp = '[^<].*?[^>]'";
        return rwsPost('/rw/rapid/' + url, data, `Failed to get RECORD name list of task ${task}`);
      };

      /**
       * Get the data structure of the specified RAPID data type
       * @alias getDataTypeStructure
       * @memberof API.RWS.RAPID
       * @param {string} typeUrl The URL of the data type.
       * @returns {Promise<any>}
       * @example
       * await API.RWS.RAPID.getDataTypeStructure();
       */
      this.getDataTypeStructure = function (blockurl = 'RAPID/robtarget', exturl = 'symbols/search') {
        let data = '';
        data += '&blockurl=' + blockurl;
        data += '&recursive=TRUE'; // True all the way
        return rwsPost('/rw/rapid/' + exturl, data, `Failed to get data structure of ${blockurl}`);
      };

      /**
       * Gets the raw value of a RAPID variable.
       * @alias getVariableRawValue
       * @memberof API.RWS.RAPID
       * @param {string} moduleName The module name.
       * @param {string} variableName The variable name.
       * @param {string} taskName The task name.
       * @returns {Promise<string>}
       * @example
       * await API.RWS.RAPID.getVariableRawValue('TestModule', 'TestVariable', 'T_ROB1');
       */
      this.getVariableRawValue = async function (moduleName, variableName, taskName = 'T_ROB1') {
        try {
          let res = await RWS.Network.get(`/rw/rapid/symbol/RAPID/${taskName}/${moduleName}/${variableName}/data`);
          let value = JSON.parse(res.responseText)['state'][0]['value'];
          return value;
        } catch (e) {
          return API.rejectWithStatus(
            `Failed to get variable value.  Task: ${taskName}. Module: ${moduleName}. Variable: ${variableName}`,
            e,
            {errorCode: ErrorCode.FailedToGetVariableValue},
          );
        }
      };

      /**
       * Gets the service routines.
       * @alias getServiceRoutines
       * @memberof API.RWS.RAPID
       * @param {string} exturl External URL for the service routines.
       * @param {boolean} allread Indicates whether to include all the readable routines.
       * @returns {Promise<any>}
       */
      this.getServiceRoutines = async function (exturl, allread) {
        try {
          let searchedResult = await RWS.Network.get('/rw/rapid/' + exturl + 'allread=' + (allread ? 'TRUE' : 'FALSE'));
          return JSON.parse(searchedResult.responseText);
        } catch (e) {
          return API.rejectWithStatus('Failed to get service routine list', e, {
            errorCode: ErrorCode.FailedToGetServiceRoutines,
          });
        }
      };

      /**
       * Sets the cycle mode for the program execution.
       * @alias setCycleMode
       * @memberof API.RWS.RAPID
       * @param {string} mode Cycle mode, e.g., 'once' or 'forever'.
       * @returns {Promise<any>}
       * @example
       * await API.RWS.RAPID.setCycleMode('forever');
       */
      this.setCycleMode = async function (mode = 'once') {
        let url = `/rw/rapid/execution/cycle`;
        let body = `cycle=${mode}`;
        return rwsPost(url, body, `Failed to set cycle mode to ${mode}`, ErrorCode.FailedToSetCycleMode, logModule);
      };

      /**
       * Gets the current cycle mode of the program execution.
       * @alias getCycleMode
       * @memberof API.RWS.RAPID
       * @returns {Promise<string>} The current cycle mode.
       * @example
       * const cycleMode = await API.RWS.RAPID.getCycleMode();
       */
      this.getCycleMode = async function () {
        try {
          let res = await RWS.Network.get('/rw/rapid/execution');
          let executionInfo = JSON.parse(res.responseText)['state'][0];
          return executionInfo['cycle'];
        } catch (e) {
          return API.rejectWithStatus('Failed to get cycle mode', e, {errorCode: ErrorCode.FailedToGetCycleMode});
        }
      };

      /**
       * This class represents a monitor on the RAPID task changes.
       * @class TaskChangeMonitor
       * @memberof API.RWS
       * @param {string} taskName The task name to be monitored.
       */
      class TaskChangeMonitor {
        constructor(task = 'T_ROB1') {
          this.url = `/rw/rapid/tasks/${encodeURIComponent(task)}`;
          this.resourceString = `/rw/rapid/tasks/${encodeURIComponent(task)};taskchange`;
          this.callbacks = [];
        }

        getTitle() {
          return this.url;
        }

        getResourceString() {
          return this.resourceString;
        }

        addCallbackOnChanged(callback) {
          if (typeof callback !== 'function') {
            throw new Error('callback is not a valid function');
          }
          this.callbacks.push(callback);
        }

        async onchanged(newValue) {
          let parsedValue = {};
          let taskChangeInfo = {};

          taskChangeInfo['changeCount'] = Object.prototype.hasOwnProperty.call(newValue, 'change-count')
            ? newValue['change-count']
            : '';
          taskChangeInfo['changeType'] = Object.prototype.hasOwnProperty.call(newValue, 'changetype')
            ? newValue['changetype']
            : '';
          taskChangeInfo['moduleName'] = Object.prototype.hasOwnProperty.call(newValue, 'module-name')
            ? newValue['module-name']
            : '';
          taskChangeInfo['programName'] = Object.prototype.hasOwnProperty.call(newValue, 'program-name')
            ? newValue['program-name']
            : '';
          taskChangeInfo['taskName'] = Object.prototype.hasOwnProperty.call(newValue, 'task-name')
            ? newValue['task-name']
            : '';

          parsedValue = taskChangeInfo;
          for (let i = 0; i < this.callbacks.length; i++) {
            try {
              this.callbacks[i](parsedValue);
            } catch (error) {
              return API.rejectWithStatus(
                'Failed to execute specific callback when RAPID task change is triggered',
                error,
                {errorCode: ErrorCode.FailedToExecuteCallback},
              );
            }
          }
        }

        subscribe() {
          return RWS.Subscriptions.subscribe([this]);
        }

        unsubscribe() {
          return RWS.Subscriptions.unsubscribe([this]);
        }
      }

      /**
       * Gets an instance of a API.RAPID.TaskChangeMonitor class
       * @alias getTaskChangeMonitor
       * @memberof API.RWS.RAPID
       * @param {string} taskName - Task name
       * @returns {Promise<object>} - API.RAPID.TaskChangeMonitor
       * @example
       * let taskChangeMonitor = await API.RWS.getTaskChangeMonitor();
       * const taskchangeCb = function (data) {
       *    console.log('RAPID is changed');
       *  };
       * taskChangeMonitor.addCallbackOnChanged(taskchangeCb);
       * taskChangeMonitor.subscribe()
       */
      this.getTaskChangeMonitor = function (taskName = 'T_ROB1') {
        return new TaskChangeMonitor(taskName);
      };
    })();

    /**
     * The API.RWS.CFG namespace provides configuration-related interfaces that are not  supported by OmniCore SDK.
     * @namespace CFG
     * @memberof API.RWS
     */
    this.CFG = new (function () {
      /**
       * Deletes an existing entry from the configuraiton database
       * @alias deleteConfigInstance
       * @memberof API.RWS.CFG
       * @param {string} name The instance name
       * @param {string} type The instance type
       * @param {string} domain The instance domain
       * @param {boolean} [requestMsh] Edit mastership needs to be requested before deleting the instance.
       * @returns {Promise<any>}
       * @example
       * // delete the signal configuration instance
       * await API.RWS.CFG.deleteConfigInstance("TestDO1", "EIO_SIGNAL", "eio")
       */
      this.deleteConfigInstance = async function (name, type, domain, requestMsh = true) {
        const f = function () {
          return RWS.Network.delete(`/rw/cfg/${domain}/${type}/instances/${name}`);
        };
        try {
          if (requestMsh) {
            return await executeWithMastership(f);
          } else {
            return await f();
          }
        } catch (e) {
          return API.rejectWithStatus(`Failed to delete configuration instance.${domain}-${type}-${name}`, e, {
            errorCode: ErrorCode.FailedToDeleteConfigInstance,
          });
        }
      };
    })();

    /**
     * The RWS.CONTROLLER class provides a set of controller-related interfaces that are not supported by the OmniCore SDK.
     * It includes methods for retrieving robot types, system information, and managing program execution settings such as speed ratio.
     * @namespace CONTROLLER
     * @memberof API.RWS
     */
    this.CONTROLLER = new (function () {
      /**
       * Gets the robot type.
       * @alias getRobotType
       * @memberof API.RWS.CONTROLLER
       * @returns {Promise<string>} A promise with a string containing the robot type.
       * @example
       * const robotType = await API.RWS.CONTROLLER.getRobotType();
       * console.log(robotType); // Outputs the robot type
       */
      this.getRobotType = async function () {
        try {
          let robotType = await RWS.Network.get('/rw/system/robottype');
          return JSON.parse(robotType.responseText)['state'][0]['robot-type'];
        } catch (e) {
          return API.rejectWithStatus('Get robot type failed', e, {errorCode: ErrorCode.FailedToGetRobotType});
        }
      };

      /**
       * Gets the system information.
       * @alias getSystemInfo
       * @memberof API.RWS.CONTROLLER
       * @returns {Promise<any>}
       * @example
       * const systemInfo = await API.RWS.CONTROLLER.getSystemInfo();
       * console.log(systemInfo);
       */
      this.getSystemInfo = async function () {
        try {
          let systemRes = await RWS.Network.get('/rw/system');
          let res = JSON.parse(systemRes.responseText);
          let result = {
            options: [],
            rwversionname: res['state'][0]['rwversionname'],
            major: res['state'][0]['major'],
            minor: res['state'][0]['minor'],
            build: res['state'][0]['build'],
            revision: res['state'][0]['revision'],
            date: res['state'][0]['date'],
            name: res['state'][0]['name'],
            sysid: res['state'][0]['sysid'],
          };
          let opts = res['_embedded']['resources'][0]['options'];
          for (const iterator of opts) {
            if (iterator['option'] != undefined) {
              result.options.push(iterator['option']);
            }
          }
          return result;
        } catch (e) {
          return API.rejectWithStatus('Failed to get controller system information', e, {
            errorCode: ErrorCode.FailedToGetSystemInfo,
          });
        }
      };

      /**
       * Sets the speed ratio for program execution. The setting requires the editing mastership and only supports the Auto mode.
       * @alias setSpeedRatio
       * @memberof API.RWS.CONTROLLER
       * @param {number} speedRatio The speed ratio number in percentage.
       * @returns {Promise<string>}
       * @note SPOC systems (e.g., RobotWare 8): This function requires write access. An error will be thrown if write access is not held.
       * @note Non-SPOC systems (e.g., RobotWare 7): Use {@link API.RWS.requestMastership} to request edit mastership before calling this API.
       * @example
       * await API.RWS.CONTROLLER.setSpeedRatio(50); // Sets the speed ratio to 50%
       */
      this.setSpeedRatio = async function (speedRatio) {
        try {
          return RWS.Network.post('/rw/panel/speedratio', 'speed-ratio=' + speedRatio.toString());
        } catch (e) {
          return API.rejectWithStatus(`Failed to set speed ratio to ${speedRatio}`, e, {
            errorCode: ErrorCode.FailedToSetSpeedRatio,
          });
        }
      };
      /**
       * Gets the speed ratio for program execution.
       * @alias getSpeedRatio
       * @memberof API.RWS.CONTROLLER
       * @returns {Promise<number>}
       * @example
       * const speedRatio = await API.RWS.CONTROLLER.getSpeedRatio();
       * console.log(`Current speed ratio: ${speedRatio}%`);
       */
      this.getSpeedRatio = async function () {
        try {
          let speedRes = await RWS.Network.get('/rw/panel/speedratio');
          return parseInt(JSON.parse(speedRes.responseText)['state'][0]['speedratio']);
        } catch (e) {
          return API.rejectWithStatus('Failed to get speed ratio', e, {errorCode: ErrorCode.FailedToGetSpeedRatio});
        }
      };
    })();

    /**
     * This class class provides a set of signal-related interfaces that are not supported by the OmniCore SDK.
     * @namespace SIGNAL
     * @memberof API.RWS
     */
    this.SIGNAL = new (function () {
      /**
       * Modifies the signal value.
       * @alias setSignalValue
       * @memberof API.RWS.SIGNAL
       * @param {string} signalName
       * @param {number} value
       * @param {string} mode value | pulse | invert | toggle | delay
       * @param {number} pulses Number of pulses. This parameter is required to be set if the mode is set to "toggle" or "pulse".
       * @param {number} activePulse Active pulse length for pulse/toggle mode, in ms
       * @param {number} passivePulse Passive pulse length for pulse/toggle mode, in ms
       * @param {number} delay Delay time for delay mode, in ms
       * @param {string} deviceType
       * @param {string} networkType
       * @param {*} attr
       * @example
       * await API.RWS.SIGNAL.setSignalValue("TestDO1","1");
       */
      this.setSignalValue = async function (
        signalName,
        value,
        {
          mode = '',
          pulses = 1,
          activePulse = 1000,
          passivePulse = 1000,
          delay = 1000,
          deviceType = '',
          networkType = '',
        } = {},
      ) {
        try {
          const isPulseParameterRequired = ['pulse', 'toggle'].includes(mode);
          const isDelayParameterRequired = ['delay'].includes(mode);
          await RWS.Network.post(
            '/rw/iosystem/signals/' +
              (networkType ? networkType + '/' : '') +
              (deviceType ? deviceType + '/' : '') +
              signalName +
              '/set-value',
            'lvalue=' +
              value +
              (mode ? `&mode=${mode}` : '') +
              (isPulseParameterRequired
                ? `&Pulses=${pulses}&ActivePulse=${activePulse}&PassivePulse=${passivePulse}`
                : '') +
              (isDelayParameterRequired ? `&Delay=${delay}` : ''),
          );
        } catch (error) {
          throw new Error(ErrorCode.FailedToSetSignalValue, {cause: error});
        }
      };

      /**
       * Gets the signal value.
       * @alias getSignalValue
       * @memberof API.RWS.SIGNAL
       * @param {string} signalName The signal name.
       * @param {string} deviceType The device type.
       * @param {string} networkType The network type.
       * @returns {Promise<string>}
       * @example
       * await API.RWS.SIGNAL.getSignalValue("TestDO1");
       */
      this.getSignalValue = async function (signalName, {deviceType = '', networkType = ''} = {}) {
        try {
          let signalRes = await RWS.Network.get(
            '/rw/iosystem/signals/' +
              (networkType ? networkType + '/' : '') +
              (deviceType ? deviceType + '/' : '') +
              signalName +
              ';state',
          );
          return JSON.parse(signalRes.responseText).state[0].lvalue;
        } catch (error) {
          return API.rejectWithStatus(`Failed to get signal ${signalName} value`, error, {
            errorCode: ErrorCode.FailedToGetSignalValue,
          });
        }
      };

      /**
       * Get the signal instance defined by RWS.IO
       * @alias getSignalInstance
       * @memberof API.RWS.SIGNAL
       * @param {string} signalName
       * @param {string} network
       * @param {string} device
       * @returns {Promise<any>}
       */
      this.getSignalInstance = async function (signalName, network = '', device = '') {
        if (network && device) {
          const rwsSignalInstance = RWS.IO.createSignal_internal(network, device, signalName);
          await rwsSignalInstance.fetch();
          return rwsSignalInstance;
        } else {
          let signalRes = await RWS.Network.get('/rw/iosystem/signals/' + signalName);
          const jsonData = JSON.parse(signalRes.responseText)['_embedded']['resources'][0];
          const path = jsonData._title.split('/');

          const UNASSIGNED_TAG = '%%UNASSIGNED%%';
          let networkName = UNASSIGNED_TAG;
          let deviceName = UNASSIGNED_TAG;
          let signal = '';
          if (path.length === 1) {
            signal = path[0];
          } else if (path.length === 3) {
            networkName = path[0];
            deviceName = path[1];
            signal = path[2];
          }

          const rwsSignalInstance = RWS.IO.createSignal_internal(networkName, deviceName, signal);
          await rwsSignalInstance.fetch();
          return rwsSignalInstance;
        }
      };
    })();

    /**
     * This class provides a set of UAS-related interfaces that are not supported by the OmniCore SDK.
     * @namespace UAS
     * @memberof API.RWS
     */
    this.UAS = new (function () {
      /**
       * Get the grants of a specified user.
       * @alias getGrantsOfUser
       * @memberof API.RWS.UAS
       * @param {string} userName The user name.
       * @example
       * await API.RWS.UAS.getGrantsOfUser("admin")
       */
      this.getGrantsOfUser = async function (userName) {
        try {
          let userGrantsInfoRes = await RWS.Network.get(`/uas/users/${userName}/grants`);
          let userGrantsInfo = JSON.parse(userGrantsInfoRes.responseText).state;

          let userGrants = [];
          userGrantsInfo.forEach((info) => {
            userGrants.push(info.grantname);
          });

          return userGrants;
        } catch (e) {
          return API.rejectWithStatus(`Failed to get grant list of user ${userName}`, e, {
            errorCode: ErrorCode.FailedToGetUserGrants,
          });
        }
      };
    })();

    /**
     * This class provides a set of user-releated interfaces.
     * @namespace USER
     * @memberof API.RWS
     */
    this.USER = new (function () {
      const RMMPStatus = {
        GRANTED: 'GRANTED',
        PENDING: 'PENDING',
        REJECTED: 'REJECTED',
      };
      /**
       * Logs out.
       * @alias logout
       * @memberof API.RWS.USER
       * @example
       * await API.RWS.USER.logout()
       */
      this.logout = async function () {
        return await RWS.Network.get('/logout');
      };
      // TODO: check the name of all user functions
      this.poller = 0;

      /**
       * Gets thw RMMP state.
       * @alias getRMMPState
       * @memberof API.RWS.USER
       * @note SPOC systems (e.g., RobotWare 8): RMMP (Remote Manual Mode Privilege) is not supported. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to request write access directly.
       * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface to cancel RMMP request.
       * @example
       * await API.RWS.USER.getRMMPState()
       */
      this.getRMMPState = async function () {
        try {
          let rmmpStates = await RWS.Network.get('/users/rmmp');
          return JSON.parse(rmmpStates.responseText)['state'][0];
        } catch (e) {
          return API.rejectWithStatus('Failed to get RMMP state', e, {errorCode: ErrorCode.FailedToGetRMMPState});
        }
      };

      /**
       * Cancels the RMMP request.
       * @alias cancelRMMP
       * @memberof API.RWS.USER
       * @note SPOC systems (e.g., RobotWare 8): RMMP (Remote Manual Mode Privilege) is not supported. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to request write access directly.
       * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface to cancel RMMP request.
       * @example
       * await API.RWS.USER.cancelRMMP()
       */
      this.cancelRMMP = async function () {
        clearInterval(this.poller);
        return await RWS.Network.post('/users/rmmp/cancel', '');
      };

      /**
       * Requests the RMMP. Release RMMP first if RMMP is held.
       * @alias reqRMMP
       * @memberof API.RWS.USER
       * @note SPOC systems (e.g., RobotWare 8): RMMP (Remote Manual Mode Privilege) is not supported. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to request write access directly.
       * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface to request RMMP.
       * @example
       * await API.RWS.USER.reqRMMP()
       */
      this.reqRMMP = async () => {
        try {
          var resRMMP = await API.RWS.USER.getRMMPState();
          if (resRMMP['rmmpheldbyme'] === 'false') {
            await API.RWS.USER.requestRMMP();
          } else if (resRMMP['rmmpheldbyme'] === 'true' && resRMMP['privilege'] != 'none') {
            // TODO: confirm if it is offical way to handle the case where TPU revoke the msh
            // The reason why add "privilege" is that it cannot request msh when rmmpheldbyme is true and privilege is 'modify'
            await API.RWS.USER.relRMMP();
            await API.RWS.USER.requestRMMP();
          }
        } catch (e) {
          return API.rejectWithStatus('Failed to request RMMP', e, {errorCode: ErrorCode.FailedToRequestRMMP});
        }
      };

      /**
       * Releases the RMMP.
       * @alias relRMMP
       * @memberof API.RWS.USER
       * @note SPOC systems (e.g., RobotWare 8): RMMP (Remote Manual Mode Privilege) is not supported. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to request write access directly.
       * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface to release RMMP.
       * @example
       * await API.RWS.USER.relRMMP()
       */
      this.relRMMP = async () => {
        try {
          let resRMMP = await API.RWS.USER.getRMMPState();
          if (resRMMP['rmmpheldbyme'] === 'false') {
            return;
          } else if (resRMMP['rmmpheldbyme'] === 'true') {
            try {
              await API.RWS.USER.cancelRMMP();
            } catch (error) {
              throw new Error('release RMMP failed');
            }
          }
        } catch (error) {
          return API.rejectWithStatus('Failed to release RMMP', error, {errorCode: ErrorCode.FailedToReleaseRMMP});
        }
      };

      /**
       * Request RMMP
       * @alias requestRMMP
       * @memberof API.RWS.USER
       * @note SPOC systems (e.g., RobotWare 8): RMMP (Remote Manual Mode Privilege) is not supported. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to request write access directly.
       * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface to request RMMP.
       * @example
       * await API.RWS.USER.requestRMMP()
       */
      this.requestRMMP = async (type = 'modify') => {
        await RWS.Network.post('/users/rmmp', `privilege=${type}`);
      };

      /**
       * Polls the RMMP state.
       * @alias pollRMMPStatus
       * @memberof API.RWS.USER
       * @note SPOC systems (e.g., RobotWare 8): RMMP (Remote Manual Mode Privilege) is not supported.
       * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface to poll RMMP status.
       * @example
       * await API.RWS.USER.pollRMMPStatus()
       */
      this.pollRMMPStatus = async () => {
        let rmmpStates = await RWS.Network.get('/users/rmmp/poll');
        return JSON.parse(rmmpStates.responseText)['state'][0];
      };

      this.pollRMMPState = () => {
        return new Promise((resolve, reject) => {
          let timeout = 500;
          clearInterval(this.poller);
          let that = this;
          this.poller = setInterval(async function () {
            try {
              var rmmp = await API.RWS.USER.pollRMMPStatus();
            } catch (error) {
              Logger.e(
                logModule,
                ErrorCode.FailedToPollRMMP,
                `Could not request edit Mastership in manual mode. >>> Failed to poll RMMP status!`,
              );
              reject('Failed to poll RMMP status!');
              return;
            }
            let rmmpStatus = rmmp.status;
            switch (rmmpStatus) {
              case RMMPStatus.GRANTED:
                clearInterval(that.poller);
                resolve(true);
                break;
              case RMMPStatus.REJECTED:
                clearInterval(that.poller);
                Logger.e(
                  logModule,
                  ErrorCode.FailedToPollRMMP,
                  `Could not request edit Mastership in manual mode. >>> RMMP rejected by local client!`,
                );
                reject('RMMP rejected');
                break;
              case RMMPStatus.PENDING:
                timeout -= 1; // 1000ms/1. 200ms/0.2.
                if (timeout < 0) {
                  await API.RWS.USER.relRMMP();
                  clearInterval(that.poller);
                  Logger.e(
                    logModule,
                    ErrorCode.FailedToPollRMMP,
                    `Could not request edit Mastership in manual mode. >>> RMMP request pending timeout!`,
                  );
                  reject('Pending timeout');
                }
                break;
              default:
            }
          }, 200);
        });
      };
    })();

    /**
     * This class provides a set of control station related interfaces that are not supported by the OmniCore SDK.
     * @namespace CONTROLSTATION
     * @memberof API.RWS
     */
    this.CONTROLSTATION = new (function () {
      const TPUName = {
        FlexPendant: 'FlexPendant',
      };

      /**
       * Returns `true` if the controller is running with RobotWare 8 (SPOC system). Use this API to check system compatibility
       * @alias isSpocSystem
       * @memberof API.RWS.CONTROLSTATION
       * @returns {Promise<boolean>}
       * @example
       * let bWithSpocSystem = await API.RWS.CONTROLSTATION.isSpocSystem()
       */
      this.isSpocSystem = async function () {
        let isSpoc = await RWS.isSpocSystem();
        return isSpoc;
      };

      const checkIfSpocSystem = async function () {
        let bSpoc = await API.RWS.CONTROLSTATION.isSpocSystem();
        if (!bSpoc) {
          throw new Error(ErrorCode.UnsupportedSystem, {
            cause: 'ControlStation functionality is only available on RobotWare 8 or later.',
          });
        }
      };
      /**
       * Returns `true` if current session is held by a control station.
       * @alias isControlStation
       * @memberof API.RWS.CONTROLSTATION
       * @returns {Promise<boolean>}
       * @example
       * let bControlStation = await API.RWS.CONTROLSTATION.isControlStation()
       */
      this.isControlStation = async function () {
        await checkIfSpocSystem();
        let type = await RWS.ControlStation.getType();
        return type !== RWS.ControlStation.ControlStationType.none;
      };

      /**
       * Returns `true` if current session is held by a local control station (e.g., FlexPendant).
       * @alias isLocalControlStation
       * @memberof API.RWS.CONTROLSTATION
       * @returns {Promise<boolean>}
       * @example
       * let bLocal = await API.RWS.CONTROLSTATION.isLocalControlStation()
       */
      this.isLocalControlStation = async function () {
        await checkIfSpocSystem();
        let type = await RWS.ControlStation.getType();
        return type == RWS.ControlStation.ControlStationType.local;
      };

      /**
       * Generate a new control station name.
       * @alias isLocalControlStation
       * @memberof API.CONTROLSTATION
       * @returns {Promise<string>}
       * @example
       * const name = await API.RWS.CONTROLSTATION.generateControlStationName()
       */
      this.generateControlStationName = async function () {
        return `WebClient_${Math.floor(Math.random() * 1e9)}`;
      };
      /**
       * Generate a new control station ID.
       * @alias generateControlStationID
       * @memberof API.RWS.CONTROLSTATION
       * @returns {Promise<string>}
       * @example
       * const id = await API.RWS.CONTROLSTATION.generateControlStationID()
       */
      this.generateControlStationID = async function () {
        return '{' + API.generateUUID() + '}';
      };

      const checkIfSameControlId = async function (id) {
        const ownId = await RWS.ControlStation.getId();
        if (id == ownId) {
          return true;
        } else {
          return false;
        }
      };

      /**
       * Checks if write access is currently held.
       * Throws an error if write access is not held.
       * Use this API to verify write access before executing operations that require write access.
       * @alias checkIfHeldWriteAccess
       * @memberof API.RWS.CONTROLSTATION
       * @returns {Promise<void>} - Resolves if write access is held, throws error otherwise
       * @example
       * await API.RWS.CONTROLSTATION.checkIfHeldWriteAccess()
       */
      this.checkIfHeldWriteAccess = async function () {
        const isSpoc = await API.RWS.CONTROLSTATION.isSpocSystem();
        if (!isSpoc) return;

        // check write access status
        const status = await RWS.ControlStation.getWriteAccessStatus();
        // check if running in FlexPendant / TPU
        if (API.isTPU()) {
          if (status.status && status.name == TPUName.FlexPendant) {
            // write access is held by current client
            return true;
          }
          throw new Error(ErrorCode.WriteAccessNotHeld, {
            cause: 'Write access is not held. Please request it through Flexpendant UI.',
          });
        } else {
          if (status.status && (await checkIfSameControlId(status.id))) {
            // write access is held by current client
            return true;
          } else if (status.status) {
            // write access is held by other client
            throw new Error(ErrorCode.WriteAccessHeldByOtherClient, {
              cause: 'Write access is held by other client. Please request it before proceeding.',
            });
          } else if (!status.externalControlEnabled) {
            // remote access is not enabled
            throw new Error(ErrorCode.ExternalControlNotEnabled, {
              cause: 'Remote Access is not enabled on the controller. Please enable it to proceed.',
            });
          }
        }

        throw new Error(ErrorCode.WriteAccessNotHeld, {
          cause: 'Write access is not held. Please request it before proceeding.',
        });
      };

      /**
       * Requests the write access.
       * This interface is only valid for RobotWare 8 or later.
       * When the control station is running in TPU / FlexPendant, request write access through the FlexPendant UI.
       * When the control station is running in browser, use this interface.
       * This interface only supports remote control station for now.
       * Throws an error if external control is not enabled or if write access is held by another client.
       * @alias requestWriteAccess
       * @memberof API.RWS.CONTROLSTATION
       * @param {boolean} [autoRegisterControlStation=false] Whether to automatically register as a remote control station if not registered when requesting write access.
       * @returns {Promise<object|void>} - Resolves if request succeeds, rejects if failure
       * @example
       * await API.RWS.CONTROLSTATION.requestWriteAccess()
       */
      this.requestWriteAccess = async function (autoRegisterControlStation = false) {
        // TODO: support local control station
        // check system compatibility
        await checkIfSpocSystem();

        // check write access status
        const status = await RWS.ControlStation.getWriteAccessStatus();
        // check if running in FlexPendant / TPU
        if (API.isTPU()) {
          if (status.status && status.name == TPUName.FlexPendant) {
            // write access is held by current client
            return;
          }
          throw new Error(ErrorCode.RequestWriteAccessThroughTPU, {
            cause: 'Failed to request write access. Please request it through Flexpendant UI.',
          });
        } else {
          if (status.status && (await checkIfSameControlId(status.id))) {
            // write access is held by current client
            return;
          } else if (status.status) {
            // write access is held by other client
            throw new Error(ErrorCode.WriteAccessHeldByOtherClient, {
              cause: 'Write access is held by other client. Please request it before proceeding.',
            });
          }
        }
        // non TPU, no write access held
        if (!status.externalControlEnabled) {
          throw new Error(ErrorCode.ExternalControlNotEnabled, {
            cause: 'Failed to request write access as remote access is not enabled on the controller.',
          });
        }
        try {
          await RWS.ControlStation.requestWriteAccess();
        } catch (e) {
          if (e.controllerStatus && e.controllerStatus.code == '-1073435871') {
            //"Operation rejected because the request is not from a Control Station."
            if (autoRegisterControlStation) {
              try {
                const name = await API.RWS.CONTROLSTATION.generateControlStationName();
                await RWS.ControlStation.registerAsRemote(name);
              } catch (e) {
                throw new Error(ErrorCode.FailedToRegisterRemoteControlStation, {
                  cause: 'Failed to register as remote control station.',
                });
              }
              try {
                await RWS.ControlStation.requestWriteAccess();
              } catch (e) {
                throw new Error(ErrorCode.FailedToRequestWriteAccess, {cause: e});
              }
            } else {
              // don't auto register control station, throw error directly
              throw new Error(ErrorCode.NotRegisteredAsControlStation, {
                cause: 'Failed to request write access as current client is not registered as a control station.',
              });
            }
          } else {
            throw new Error(ErrorCode.FailedToRequestWriteAccess, {cause: e});
          }
        }
      };

      /**
       * Releases the write access.
       * This interface is only valid for RobotWare 8 or later.
       * When the control station is running in TPU / FlexPendant, release write access through the FlexPendant UI.
       * When the control station is running in browser, use this interface.
       * This interface only supports remote control station for now. And it should be called when write access is no longer needed or before logging out.
       * @alias releaseWriteAccess
       * @memberof API.RWS.CONTROLSTATION
       * @returns {Promise<object|void>} - Resolves if release succeeds, rejects if failure
       * @example
       * await API.RWS.CONTROLSTATION.releaseWriteAccess()
       */
      this.releaseWriteAccess = async function () {
        // check system compatibility
        await checkIfSpocSystem();

        // check write access status
        const status = await RWS.ControlStation.getWriteAccessStatus();
        // check if running in FlexPendant / TPU
        if (API.isTPU()) {
          if (!(status.status && status.name == TPUName.FlexPendant)) {
            return;
          }
          throw new Error(ErrorCode.ReleaseWriteAccessThroughTPU, {
            cause: 'Failed to release write access. Please release it through Flexpendant UI.',
          });
        } else {
          if (!(status.status && (await checkIfSameControlId(status.id)))) {
            return;
          }
        }

        try {
          await RWS.ControlStation.releaseWriteAccess();
        } catch (e) {
          if (e.controllerStatus && e.controllerStatus.code == '-1073435871') {
            throw new Error(ErrorCode.NotRegisteredAsControlStation, {
              cause: 'Failed to release write access as current client is not registered as a control station.',
            });
          } else {
            throw new Error(ErrorCode.FailedToReleaseWriteAccess, {cause: e});
          }
        }
      };

      /**
       * Ensures motion control is allowed in SPOC systems.
       * Checks if motion control is already allowed, and if not, requests it automatically.
       * This interface is only valid for RobotWare 8 or later and remote control stations.
       * @alias ensureMotionControlAllowed
       * @memberof API.RWS.CONTROLSTATION
       * @returns {Promise<void>} - Resolves if motion control is ensured to be allowed, rejects if failure
       * @example
       * await API.RWS.CONTROLSTATION.ensureMotionControlAllowed()
       */
      this.ensureMotionControlAllowed = async function () {
        await checkIfSpocSystem();

        if (API.isTPU()) {
          return;
        }

        try {
          const isAllowed = await RWS.ControlStation.isMotionControlAllowed();
          if (!isAllowed) {
            await RWS.ControlStation.allowMotionControl(true);
          }
        } catch (e) {
          throw new Error(ErrorCode.FailedToAllowMotionControl, {cause: e});
        }
      };

      const subscribedResources = {};
      /**
       * Enum for monitor resources
       */
      this.MonitorResources = {
        'write-access-status': 'write-access-status',
        'release-appeal-counter': 'release-appeal-counter',
        'tpu-safety-protocol-connected': 'tpu-safety-protocol-connected',
        'local-is-connected': 'local-is-connected',
      };

      const subscribeRes = async function (monitor, func) {
        if (rws.isSubscriptionBlocked) {
          Logger.w(
            WarningType.RWSSubscriptionBlocked,
            'API.RWS.CONTROLSTATION: Subscription disabled when trying to monitor control station resource',
          );
          return;
        }
        monitor.addCallbackOnChanged(func);
        await monitor.subscribe(true);
      };

      /**
       * Monitors a control station resource.
       * @alias monitorResource
       * @memberof API.RWS.CONTROLSTATION
       * @param {string} resourceType The type of resource to monitor (from MonitorResources enum).
       * @param {function} [callback] The callback function that is called when the resource changes.
       * @returns {Promise<void>}
       * @example
       * API.RWS.CONTROLSTATION.monitorResource(
       *   API.RWS.CONTROLSTATION.MonitorResources['write-access-status'],
       *   (status) => {
       *     console.log('Status:', status);
       *   }
       * );
       */
      this.monitorResource = async function (resourceType, callback = null) {
        await checkIfSpocSystem();

        const mappingKey = Store.generateMappingKey('ControlStation', [resourceType]);

        // Validate if resourceType is a valid monitor resource
        if (!Object.keys(this.MonitorResources).includes(resourceType)) {
          Logger.w(WarningType.RWSSubscriptionBlocked, `API.RWS.CONTROLSTATION: Unknown ${mappingKey}`);
          return;
        }

        if (rws.isFetchControllerBlocked) {
          Logger.w(
            WarningType.RWSRequestBlocked,
            `API.RWS.CONTROLSTATION: HttpRequest disabled when trying to monitor ${mappingKey}`,
          );
          return;
        }

        const storeValue = Store.getNamespace(ControlStation).getMapping(mappingKey);
        if (storeValue === undefined) {
          try {
            Store.getNamespace(ControlStation).setMapping(mappingKey, Pending);
            let initialValue;
            switch (resourceType) {
              case 'write-access-status':
                initialValue = await RWS.ControlStation.getWriteAccessStatus();
                break;

              case 'release-appeal-counter':
                initialValue = await RWS.ControlStation.getWriteAccessReleaseAppealChangeCounter();
                break;

              case 'tpu-safety-protocol-connected':
                initialValue = await RWS.ControlStation.getTPUSafetyProtocolStatus();
                break;

              case 'local-is-connected':
                initialValue = await RWS.ControlStation.isLocalConnected();
                break;

              default:
                throw new Error(`Unknown resource type: ${resourceType}`);
            }
            Store.getNamespace(ControlStation).setMapping(mappingKey, initialValue);
            typeof callback === 'function' && callback(initialValue);
            rws._events.trigger(mappingKey + '_fetch', initialValue);
          } catch (e) {
            return API.rejectWithStatus(
              `Failed to subscribe to control station resource ${resourceType}`,
              e,
              ErrorCode.FailedToSubscribeControlStation,
            );
          }
        } else if (storeValue === Pending) {
          rws._events.once(mappingKey + '_fetch', (value) => {
            typeof callback === 'function' && callback(value);
          });
        } else {
          typeof callback === 'function' && callback(storeValue);
        }
        if (rws.isSubscriptionBlocked) {
          Logger.w(
            WarningType.RWSSubscriptionBlocked,
            `API.CONTROLSTATION: Subscription disabled when trying to monitor control station resource ${mappingKey}`,
          );
          return;
        }
        // if the resource is not subscribed yet, subscribe to it
        rws._events.on(mappingKey, callback);
        if (!subscribedResources[mappingKey]) {
          try {
            const monitor = RWS.ControlStation.getMonitor(resourceType);

            const cbResource = function (data) {
              Logger.i(logModule, `Control station resource changed: ${resourceType}`);
              Store.getNamespace(ControlStation).setMapping(mappingKey, data);
              rws._events.trigger(mappingKey, data);
              Logger.i(InfoType.RobotOperation, `Triggered control station resource: ${resourceType}`);
            };

            subscribeRes(monitor, cbResource.bind(this));
            subscribedResources[mappingKey] = true;

            Logger.i(InfoType.RobotOperation, `Control station resource subscribed:${mappingKey}`);
          } catch (e) {
            return API.rejectWithStatus(
              `Failed to subscribe to control station resource ${resourceType}`,
              e,
              ErrorCode.FailedToSubscribeControlStation,
            );
          }
        }
      };

      /**
       * Monitors the write access status of control station. The callback receives an object with `status` (boolean) and `name` (string) properties indicating who holds write access.
       * @alias monitorWriteAccessStatus
       * @memberof API.RWS.CONTROLSTATION
       * @param {function} [callback] The callback function that is called when write access status changes.
       * @returns {Promise<void>}
       * @example
       * API.RWS.CONTROLSTATION.monitorWriteAccessStatus((status) => {
       *   console.log('Write access status:', status);
       *   if (status.status) {
       *     console.log('Write access held by:', status.name);
       *   }
       * });
       */
      this.monitorWriteAccessStatus = async function (callback = null) {
        return this.monitorResource(this.MonitorResources['write-access-status'], callback);
      };

      /**
       * Monitors the write access release appeal counter. The counter increments when another client requests write access currently held by the current client.
       * @alias monitorReleaseAppealCounter
       * @memberof API.RWS.CONTROLSTATION
       * @param {function} [callback] The callback function that is called when counter changes.
       * @returns {Promise<void>}
       * @example
       * API.RWS.CONTROLSTATION.monitorReleaseAppealCounter((counter) => {
       *   console.log('Release appeal counter:', counter);
       * });
       */
      this.monitorReleaseAppealCounter = async function (callback = null) {
        return this.monitorResource(this.MonitorResources['release-appeal-counter'], callback);
      };

      /**
       * Monitors the TPU safety protocol connection status. Returns `true` when a local control station with safety protocol is connected.
       * @alias monitorTPUSafetyProtocolConnected
       * @memberof API.RWS.CONTROLSTATION
       * @param {function} [callback] The callback function that is called when connection status changes.
       * @returns {Promise<void>}
       * @example
       * API.RWS.CONTROLSTATION.monitorTPUSafetyProtocolConnected((isConnected) => {
       *   console.log('TPU safety protocol connected:', isConnected);
       * });
       */
      this.monitorTPUSafetyProtocolConnected = async function (callback = null) {
        return this.monitorResource(this.MonitorResources['tpu-safety-protocol-connected'], callback);
      };

      /**
       * Monitors the local control station connection status. Returns `true` when a local control station is connected to the controller.
       * @alias monitorLocalIsConnected
       * @memberof API.RWS.CONTROLSTATION
       * @param {function} [callback] The callback function that is called when connection status changes.
       * @returns {Promise<void>}
       * @example
       * API.RWS.CONTROLSTATION.monitorLocalIsConnected((isConnected) => {
       *   console.log('Local control station connected:', isConnected);
       * });
       */
      this.monitorLocalIsConnected = async function (callback = null) {
        return this.monitorResource(this.MonitorResources['local-is-connected'], callback);
      };
    })();
  })();

  rws.constructedRWS = true;
};

if (typeof API.constructedRWS === 'undefined') {
  factoryApiRws(API);
}

export default API;
export {factoryApiRws};