ecosystem-motion.js

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

export const factoryApiMotion = function (mot) {
  const logModule = 'ecosystem-motion';

  /**
   * The API.MOTION class provides a comprehensive set of interfaces for controlling and monitoring robot motion.
   * It includes methods for jogging, aligning, and moving the robot to specific positions, as well as retrieving and setting tools, work objects, and coordinate systems.
   * This class simplifies motion control operations, enabling developers to efficiently interact with and manage the robot's movement and positioning.
   * @alias API.MOTION
   * @namespace
   */
  mot.MOTION = new (function () {
    let ccounter = null;
    let jogStop = false;
    let isJogging = false;
    let jogTimeout = 0;

    /**
     * @alias JOGMODE
     * @memberof API.MOTION
     * @readonly
     * @enum {string}
     */
    this.JOGMODE = {
      Align: 'Align',
      GoToPos: 'GoToPos',
      ConfigurationJog: 'ConfigurationJog',
      Cartesian: 'Cartesian',
      AxisGroup1: 'AxisGroup1',
      AxisGroup2: 'AxisGroup2',
      Current: '',
    };

    /**
     * @alias COORDS
     * @memberof API.MOTION
     * @readonly
     * @enum {string}
     * @property {enum} Wobj The work object coordinate system
     * @property {enum} Base The Base coordinate system
     * @property {enum} Tool The Tool coordinate system
     * @property {enum} World The World coordinate system
     * @property {enum} Current The active coordinate system
     */
    this.COORDS = {
      Wobj: 'Wobj',
      Base: 'Base',
      Tool: 'Tool',
      World: 'World',
      Current: '',
    };

    this.MaxJogAxisSpeed = 2000;
    /**
     * @alias POSITIONTYPES
     * @memberof API.MOTION
     * @readonly
     * @enum {number}
     */
    this.POSITIONTYPES = {
      robtarget: 1,
      jointtarget: 2,
    };

    // /**
    //  * @typedef {number} JogDataIdx0
    //  * @typedef {number} JogDataIdx1
    //  * @typedef {number} JogDataIdx2
    //  * @typedef {number} JogDataIdx3
    //  * @typedef {number} JogDataIdx4
    //  * @typedef {number} JogDataIdx5
    //  * @type {[JogDataIdx0, JogDataIdx1, JogDataIdx2, JogDataIdx3, JogDataIdx4, JogDataIdx5]} JogData
    //  */

    /**
     * @typedef Trans
     * @prop {number} x
     * @prop {number} y
     * @prop {number} z
     * @memberof API.MOTION
     */

    /**
     * @typedef Rot
     * @prop {number} q1
     * @prop {number} q2
     * @prop {number} q3
     * @prop {number} q4
     * @memberof API.MOTION
     */

    /**
     * @typedef RobConf
     * @prop {number} cf1
     * @prop {number} cf4
     * @prop {number} cf6
     * @prop {number} cfx
     * @memberof API.MOTION
     */

    /**
     * @typedef ExtAx
     * @prop {number}  eax_a
     * @prop {number}  eax_b
     * @prop {number}  eax_c
     * @prop {number}  eax_d
     * @prop {number}  eax_e
     * @prop {number}  eax_f
     * @memberof API.MOTION
     */

    /**
     * @typedef RobTarget
     * @prop {Trans} trans
     * @prop {Rot} rot
     * @prop {RobConf} robconf
     * @prop {ExtAx} extax
     * @memberof API.MOTION
     */

    /**
     * @typedef RobAx
     * @prop {number} rax_1
     * @prop {number} rax_2
     * @prop {number} rax_3
     * @prop {number} rax_4
     * @prop {number} rax_5
     * @prop {number} rax_6
     * @memberof API.MOTION
     */

    /**
     * @typedef JointTarget
     * @prop {RobAx} robax
     * @prop {ExtAx} extax
     * @memberof API.MOTION
     */

    /**
     * @typedef executeJoggingProps
     * @prop {string} [tool]
     * @prop {string} [wobj]
     * @prop {COORDS} [coords]
     * @prop {JOGMODE} [jogMode]
     * @prop {JogData} [jogData]
     * @prop {RobTarget} [robTarget]
     * @prop {JointTarget} [jointTarget]
     * @prop {boolean} [doJoint]
     * @memberof API.MOTION
     */

    /**
     * Jogs the robot
     * Non-SPOC systems (e.g., RobotWare 7):
     * The interface shall work when the two conditions are met:
     * 1. Operation mode is manual reduce mode
     * 2. User logins as local client
     * SPOC systems (e.g., RobotWare 8):
     * The interface shall work when the two conditions are met:
     * 1. Operation mode is manual reduce mode
     * 2. Write access is held
     * @alias executeJogging
     * @memberof API.MOTION
     * @param {JOGMODE} jogMode - Jogging mode
     * @param {Object} [options] - Optional jogging options
     * @param {JogData} [options.jogData=[500,500,500,500,500,500]] - Jog speed/axis data
     * @param {RobTarget|JointTarget|null} [options.targetPosition=null] - Target position
     * @param {string} [options.tool=''] - Tool identifier
     * @param {string} [options.wobj=''] - Work object identifier
     * @param {COORDS|string} [options.coords=''] - Coordinate system
     * @returns {Promise<object|void>} - Resolves if jogging succeeds, rejects if failure
     * @note SPOC systems (e.g., RobotWare 8): Write access must be held before executing jogging. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
     * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as motion mastership is handled internally.
     * @example
     * // Perform aligning
     * await API.MOTION.executeJogging(API.MOTION.JOGMODE.Align)
     */
    this.executeJogging = async function (
      jogMode,
      {jogData = [500, 500, 500, 500, 500, 500], targetPosition = null, tool = '', wobj = '', coords = ''} = {},
    ) {
      try {
        isJogging = true;
        Logger.i(InfoType.RobotOperation, 'Perfroming jogging task with API.MOTION.executeJogging');
        jogStop = true;
        await API.RWS.requestMastership('motion');

        await prepareJogging(tool, wobj, coords, jogMode);

        await doJogging(jogData, targetPosition);
      } catch (err) {
        isJogging = false;
        let bHeldMsh = await API.RWS.checkIfHeldMastership('motion');
        if (bHeldMsh) {
          await API.RWS.releaseMastership('motion');
        }
        throw err;
      }
    };

    /**
     * Prepares the MechUnit and ChangeCounter for jogging
     * @alias prepareJogging
     * @memberof API.MOTION
     * @param {string} tool
     * @param {string} wobj
     * @param {string} coords
     * @param {string} jogMode
     * @returns {undefined |Promise<{}>} - Undefined or reject Promise if fails.
     * @private
     */
    const prepareJogging = async function (tool = '', wobj = '', coords = '', jogMode = '') {
      const isSpoc = await API.RWS.CONTROLSTATION.isSpocSystem();
      let opMode = await API.CONTROLLER.getOperationMode();
      let ctrlState = await API.CONTROLLER.getControllerState();
      if (isSpoc) {
        if (!(ctrlState === API.CONTROLLER.STATE.MotorsOn && opMode === API.CONTROLLER.OPMODE.ManualR)) {
          // check if registered as local control station
          const isLocal = await RWS.ControlStation.isLocalConnected();
          if (!isLocal) {
            throw new Error(ErrorCode.MissingConditionsToJog, {
              cause: `Mismatched conditions to jog: control state - ${ctrlState}, operation mode - ${opMode}`,
            });
          }
        }
      } else {
        if (ctrlState === API.CONTROLLER.STATE.MotorsOn && opMode === API.CONTROLLER.OPMODE.ManualR) {
          // check if registered in local client
          let isLocalClient = await API.UAS.isLoggedInAsLocalClient();
          if (!isLocalClient) {
            throw new Error(ErrorCode.MissingPermissionsToJog, {
              cause: 'Jogging in manual mode is restricted to the local client.',
            });
          }
        } else {
          throw new Error(ErrorCode.MissingConditionsToJog, {
            cause: `Mismatched conditions to jog: control state - ${ctrlState}, operation mode - ${opMode}`,
          });
        }
      }

      await API.RWS.MOTIONSYSTEM.setMechunit({tool, wobj, jogMode, coords});

      ccounter = await API.RWS.MOTIONSYSTEM.getChangeCount();
    };

    /**
     * Cyclic execution during jogging
     * @alias doJogging
     * @memberof API.MOTION
     * @private
     * @param {API.MOTION.JogData} jogData
     * @param {API.MOTION.RobTarget | API.MOTION.JointTarget} targetPosition
     * @param {boolean} once - If true, send jog command only once
     * @returns {undefined |Promise<{}>} - Undefined or reject Promise if fails.
     */
    const doJogging = async function (jogData, targetPosition = null, once = false) {
      jogStop = false;
      clearTimeout(jogTimeout);

      if (targetPosition !== null) {
        if (bJointTarget(targetPosition)) {
          await API.RWS.MOTIONSYSTEM.setRobotPositionJoint(targetPosition);
        } else {
          await API.RWS.MOTIONSYSTEM.setRobotPositionTarget(targetPosition);
        }
      }
      try {
        await API.MOTION.sendJogsinLoop(jogData, once);
      } catch (err) {
        jogStop = true;
        throw err;
      }
    };

    /**
     * Send once/loop jog commands
     * @alias sendJogsinLoop
     * @memberof API.MOTION
     * @private
     * @param {API.MOTION.JogData} jogData
     * @param {boolean} once - If true, send jog command only once
     * @returns {undefined |Promise<{}>} - Undefined or reject Promise if fails.
     */
    this.sendJogsinLoop = async function (jogData, once = false) {
      await API.RWS.MOTIONSYSTEM.jog(jogData, ccounter);
      if (once) jogStop = true;
      if (!jogStop) {
        Logger.i(logModule, 'looping jog commands');
        jogTimeout = setTimeout(async () => await this.sendJogsinLoop(jogData, once), 200);
      }
    };

    /**
     * Stops any running jog
     * @alias stopJogging
     * @memberof API.MOTION
     * @returns {undefined |Promise<{}>} Resolves if stopped correctly, rejects if failed.
     * @example
     * await API.MOTION.stopJogging()
     */
    this.stopJogging = async function () {
      if (isJogging) {
        clearTimeout(jogTimeout);
        const jogData = [0, 0, 0, 0, 0, 0];
        jogStop = true;
        try {
          await API.RWS.MOTIONSYSTEM.jog(jogData, ccounter);
        } finally {
          await API.RWS.releaseMastership('motion');
        }
        isJogging = false;
      }
    };

    /**
     * Gets the current position of the robot
     * @alias getRobotPosition
     * @memberof API.MOTION
     * @returns {Promise<object>} - An object containing current position of the robot
     * @example
     * await API.MOTION.getRobotPosition()
     */
    this.getRobotPosition = async function () {
      let robTarget = await API.RWS.MOTIONSYSTEM.getRobTarget();
      return robTarget;
    };

    /**
     * Gets the current tool
     * @alias getTool
     * @memberof API.MOTION
     * @returns {Promise<object>} - Object containing current position of the robot
     * @example
     * await API.MOTION.getTool()
     */
    this.getTool = async function () {
      const mechUnit = await API.RWS.MOTIONSYSTEM.getMechunit();
      return mechUnit.tool;
    };

    /**
     * Gets the active work object
     * @alias getWobj
     * @memberof API.MOTION
     * @returns {Promise<object>} - An object containing current position of the robot
     * @example
     * await API.MOTION.getWobj()
     */
    this.getWobj = async function () {
      const mechUnit = await API.RWS.MOTIONSYSTEM.getMechunit();
      return mechUnit.wobj;
    };

    /**
     * Gets the current coordinate system
     * @alias getCoord
     * @memberof API.MOTION
     * @returns {Promise<string>} A string describing the current coordinate system of the robot.
     * @example
     * await API.MOTION.getCoord()
     */
    this.getCoord = async function () {
      const mechUnit = await API.RWS.MOTIONSYSTEM.getMechunit();
      return mechUnit.coords;
    };

    /**
     * Gets the axis number of the robot.
     * @alias getAxisNumber
     * @memberof API.MOTION
     * @returns {Promise<number>} Axis number of the robot.
     * @example
     * let axisNum = await API.MOTION.getAxisNumber()
     */
    this.getAxisNumber = async function () {
      if (typeof this.axisNumber == 'undefined') {
        const mechUnit = await API.RWS.MOTIONSYSTEM.getMechunit();
        this.axisNumber = parseInt(mechUnit.axes);
      }
      return this.axisNumber;
    };

    /**
     * Sets the active tool to the specified value
     * @alias setTool
     * @memberof API.MOTION
     * @param {string} tool Name of the tool
     * @returns {Promise<object>} Resolves if setting correctly, rejects if failed.
     * @note SPOC systems (e.g., RobotWare 8): Write access must be held before deleting setting active tool. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
     * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as motion mastership is handled internally.
     * @example
     * await API.MOTION.setTool("tool0")
     */
    this.setTool = async function (tool) {
      try {
        await API.RWS.requestMastership('motion');
        try {
          await API.RWS.MOTIONSYSTEM.setMechunit({tool});
        } catch (error) {
          return API.rejectWithStatus('Failed to set tool', {}, {errorCode: ErrorCode.FailedToSetActiveTool});
        }
      } finally {
        await API.RWS.releaseMastership('motion');
      }
    };

    /**
     * Sets the active work object to the specified value.
     * @alias setWobj
     * @memberof API.MOTION
     * @param {string} value Name of the work object.
     * @returns {Promise<object>} Resolves if setting correctly, rejects if failed.
     * @note SPOC systems (e.g., RobotWare 8): Write access must be held before deleting setting active wobj. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
     * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as motion mastership is handled internally.
     * @example
     * await API.MOTION.setWobj("wobj0")
     */
    this.setWobj = async function (value) {
      try {
        await API.RWS.requestMastership('motion');
        try {
          await API.RWS.MOTIONSYSTEM.setMechunit({wobj: value});
        } catch (error) {
          return API.rejectWithStatus('Failed to set work object', {}, {errorCode: ErrorCode.FailedToSetActiveWobj});
        }
      } finally {
        await API.RWS.releaseMastership('motion');
      }
    };

    // TODO: add stopJogging as mouseup event for Align functionality
    /**
     * Performs align task
     * Non-SPOC systems (e.g., RobotWare 7):
     * The interface shall work when the two conditions are met:
     * 1. Operation mode is manual reduce mode
     * 2. User logins as local client
     * SPOC systems (e.g., RobotWare 8):
     * The interface shall work when the two conditions are met:
     * 1. Operation mode is manual reduce mode
     * 2. Write access is held
     * @alias align
     * @memberof API.MOTION
     * @param {number} speedRatio Align speed ratio, whose value range is 1 to 100.
     * @param {API.MechUnits} tool If empty, use active tool as align tool
     * @param {API.MechUnits} wobj If empty, use active wobj as align wobj
     * @param {API.MechUnits} coord	If empty, use active coord as align coord
     * @returns {undefined |Promise<{}>} - Resolves if aligning succeeds, rejects if fails
     * @note SPOC systems (e.g., RobotWare 8): Write access must be held before executing performing align. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
     * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as motion mastership is handled internally.
     * @example
     * await API.MOTION.align(10)
     */
    this.align = async function (speedRatio, {tool = '', wobj = '', coords = ''} = {}) {
      try {
        Logger.i(InfoType.RobotOperation, 'Perfroming align task with API.MOTION.align');
        let speed = speedRatio > 100 ? 100 : speedRatio < 2 ? 2 : speedRatio;
        let speedValue = (this.MaxJogAxisSpeed * speed) / 100;
        let jogData = [0, 0, 0, 0, 0, 0];

        // check axis number
        let axisNumber = await this.getAxisNumber();
        if (axisNumber == 4) {
          jogData = [speedValue, speedValue, speedValue, speedValue, 0, 0];
        } else {
          jogData = [speedValue, speedValue, speedValue, speedValue, speedValue, speedValue];
        }

        const jogMode = API.MOTION.JOGMODE.Align;

        await API.MOTION.executeJogging(jogMode, {jogData, tool, wobj, coords});
      } catch (e) {
        isJogging = false;
        throw e;
      }
    };

    // TODO: add stopJogging as mouseup event for GoTo functionality
    /**
     * Executes the GOTO task.
     * Non-SPOC systems (e.g., RobotWare 7):
     * The interface shall work when the two conditions are met:
     * 1. Operation mode is manual reduce mode
     * 2. User logins as local client
     * SPOC systems (e.g., RobotWare 8):
     * The interface shall work when the two conditions are met:
     * 1. Operation mode is manual reduce mode
     * 2. Write access is held
     * @alias goToPosition
     * @memberof API.MOTION
     * @param {number} speedRatio GOTO movement speed ratio, whose value range is 1 to 100.
     * @param {RobTarget | JointTarget} targetPosition - The target location of the GOTO task
     * @param {API.MechUnits} tool If empty, use active tool as align tool
     * @param {API.MechUnits} wobj If empty, use active wobj as align wobj
     * @param {API.MechUnits} coord	If empty, use active coord as align coord
     * @returns {undefined |Promise<{}>} - Resolves if the motion task is executed, rejects if failed
     * @note SPOC systems (e.g., RobotWare 8): Write access must be held before executing performing GoTo. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
     * @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as motion mastership is handled internally.
     * @example
     * let variableValue = await API.RAPID.getVariableValue("MainModule","pos1");
     * await API.MOTION.goToPosition(10, variableValue)
     */
    this.goToPosition = async function (speedRatio, targetPosition, {tool = '', wobj = '', coords = ''} = {}) {
      try {
        Logger.i(InfoType.RobotOperation, 'Performing Goto task with API.MOTION.goToPosition');
        if (!targetPosition) {
          Logger.w(WarningType.ControllerOperation, 'Empty target position');
          return;
        }
        if (!bRobotTarget(targetPosition) && !bJointTarget(targetPosition)) {
          Logger.w(WarningType.ControllerOperation, 'Invalid target position');
          return;
        }

        const speed = speedRatio > 100 ? 100 : speedRatio < 2 ? 2 : speedRatio;
        let speedValue = (this.MaxJogAxisSpeed * speed) / 100;
        let jogData = [0, 0, 0, 0, 0, 0];

        // check axis number
        let axisNumber = await this.getAxisNumber();
        if (axisNumber == 4) {
          jogData = [speedValue, speedValue, speedValue, speedValue, 0, 0];
        } else {
          jogData = [speedValue, speedValue, speedValue, speedValue, speedValue, speedValue];
        }

        const jogMode = API.MOTION.JOGMODE.GoToPos;

        await API.MOTION.executeJogging(jogMode, {jogData, targetPosition, tool, wobj, coords});
      } catch (e) {
        isJogging = false;
        throw e;
      }
    };

    /**
     * Teaches a robot position.
     * @alias teachPosition
     * @memberof API.MOTION
     * @param {API.MOTION.POSITIONTYPES} type Position type
     * @param {string} tool The tool used to teach position
     * @param {string} wobj The wobj used to teach position
     * @param {string} coords The coordinate system used to teach position
     * @param {string} mechunit The mechanical unit used to teach position
     * @returns {undefined | Promise<{RobTarget}> | Promise<{JointTarget}>} - The teached position
     * @example
     * let position = await API.MOTION.teachPosition(API.MOTION.POSITIONTYPES.robtarget);
     */
    this.teachPosition = async function (type, {tool = '', wobj = '', coords = '', mechunit = 'ROB_1'} = {}) {
      let currentPosition;
      Logger.i(InfoType.RobotOperation, 'Teaching current robot position with API.MOTION.teachPosition');
      try {
        if (type == this.POSITIONTYPES.robtarget) {
          currentPosition = await API.RWS.MOTIONSYSTEM.getRobTarget(tool, wobj, coords, mechunit);
        } else if (type == this.POSITIONTYPES.jointtarget) {
          currentPosition = await API.RWS.MOTIONSYSTEM.getJointTarget(mechunit);
        }
        return currentPosition;
      } catch (error) {
        return API.rejectWithStatus('Failed to teach current robot position', error, {
          errorCode: ErrorCode.FailedToTeachPosition,
        });
      }
    };

    const bJointTarget = function (position) {
      return position.robax && position.extax;
    };

    const bRobotTarget = function (position) {
      return position.trans && position.rot && position.robconf && position.extax;
    };
  })();

  mot.constructedMotion = true;
};

if (typeof API.constructedMotion === 'undefined') {
  factoryApiMotion(API);
}

export default API;