import API from './ecosystem-base.js';
import {Logger} from './../function/log-helper.js';
import {ErrorCode, ExceptionIdMap} from './../exception/exceptionDesc.js';
import {InfoType, WarningType} from '../information/informationCode.js';
import {appendDataToErrInstance} from '../utils/utils.js';
export const factoryApiRapid = function (r) {
const logModule = 'ecosystem-rapid';
/**
* The API.RAPID namespace provides a comprehensive set of interfaces for managing and interacting with RAPID programs on the controller.
* It includes methods for controlling program execution, managing tasks, modules, and variables, as well as monitoring execution states and subscribing to variable changes.
* This class simplifies the development and integration of RAPID-based automation solutions, enabling developers to efficiently interact with and control RAPID programs.
* @alias API.RAPID
* @namespace
*/
r.RAPID = new (function () {
this.variables = [];
this.subscriptions = new Map();
this.variableInstanceMap = new Map();
/**
* @alias MODULETYPE
* @memberof API.RAPID
* @readonly
* @property {enum} Program Program module
* @property {enum} System System module
* @enum {string}
*/
const MODULETYPE = {
Program: 'program',
System: 'system',
};
this.MODULETYPE = MODULETYPE;
const EXECUTIONSTATE = {
Running: 'running',
Stopped: 'stopped',
};
this.EXECUTIONSTATE = EXECUTIONSTATE;
/**
* @memberof API
* @param {RWS.Rapid.MonitorResources} res
* @param {Function} func
* @param {string} [task]
* @returns {Promise<any>}
* @private
*/
const subscribeRes = async function (res, func, task = 'T_ROB1') {
try {
if (API.isSubscriptionBlocked) {
Logger.w(WarningType.RWSSubscriptionBlocked, 'API.RAPID: Subscription ON rapid resource is disabled');
return;
}
const monitor = RWS.Rapid.getMonitor(res, task);
monitor.addCallbackOnChanged(func);
await monitor.subscribe();
} catch (e) {
return API.rejectWithStatus(`Failed to subscribe to ${res}`, e, {
errorCode: ErrorCode.RWSSubscriptionFailed,
msgParams: {resourceName: RWS.Rapid.MonitorResources.execution},
});
}
};
/**
* @typedef searchSymbolProps
* @prop {string} [task] The task where the searching runs. (default: 'T_ROB1')
* @prop {string} [module] The module where the searching runs.
* @prop {boolean} [isInUse] Only return symbols that are used in a RAPID program,
* i.e. a type declaration that has no declared variable will not be returned when this flag is set true.
* @prop {object} [dataType] Type of the data, e.g. 'num'(only one data type is supported)
* @prop {number} [symbolType] Can be the following values:
* <br> undefined: 0
* <br> constant: 1
* <br> variable: 2
* <br> persistent: 4
* <br> function: 8
* <br> procedure: 16
* <br> trap: 32
* <br> module: 64
* <br> task: 128
* <br> routine: 8 + 16 + 32
* <br> rapidData: 1 + 2 + 4
* <br> any: 255
* @prop {string | Array} [name] Name of the data symbol (not case-sensitive)
* @memberof API.RAPID
*/
/**
* @alias searchSymbol
* @memberof API.RAPID
* @param {searchSymbolProps} props
* @returns {Promise<RWS.Rapid.SymbolSearchResult[]>}
* A Promise with list of objects. Each object contains:
* <br> (string) name name of the data symbol
* <br> ([string]) scope symbol scope
* <br> (string) symbolType type of the symbol, e.g. 'pers'
* <br> (string) dataType type of the data, e.g. 'num'
* @private
* @todo not yet working properly
*/
const searchSymbol = async function ({
task = 'T_ROB1',
module = null,
isInUse = false,
dataType = '',
symbolType = RWS.Rapid.SymbolTypes.rapidData,
name = '',
} = {}) {
let elements = [];
try {
var properties = RWS.Rapid.getDefaultSearchProperties();
let url = `RAPID/${task}`;
if (module) url = url + `/${module}`;
properties.searchURL = url;
properties.types = symbolType;
properties.isInUse = isInUse;
var hits = [];
if (name instanceof Array) {
for (const index in name) {
const regexp = name[index] !== '' ? `^.*${name[index]}.*$` : '';
hits.push(...(await RWS.Rapid.searchSymbols(properties, dataType, regexp)));
}
} else {
const regexp = name !== '' ? `^.*${name}.*$` : '';
hits = await RWS.Rapid.searchSymbols(properties, dataType, regexp);
}
if (hits.length > 0) {
for (let i = 0; i < hits.length; i++) {
elements.push(hits[i]);
}
}
return elements;
} catch (e) {
return API.rejectWithStatus(`Failed to search symbol ${name} - module: ${module}`, e, {
errorCode: ErrorCode.FailedToSearchSymbol,
});
}
};
/**
* Gets available value-type RAPID RECORDs.
* @alias getValRecordTypes
* @memberof API.RAPID
* @param {string} [task] The task name.
* @returns {Promise<object>}
* @example
* await API.RAPID.getVariableTypes('T_ROB1')
*/
this.getValRecordTypes = async function (task = 'T_ROB1') {
try {
let types = [];
let searchFlag = false;
let exturl = 'symbols/search';
while (!searchFlag) {
let searchedResult = await API.RWS.RAPID.getRecordTypes(task, exturl);
let resources = searchedResult['_embedded']['resources'];
for (let i = 0; i < resources.length; i++) {
if (resources[i].valtyp == 'val') {
types.push(resources[i].name);
}
}
if (typeof searchedResult._links.next == 'undefined') {
searchFlag = true;
} else {
exturl = searchedResult._links.next.href;
}
}
return types;
} catch (e) {
return API.rejectWithStatus(`Failed to get RAPID value-type RECORD types in task ${task}`, e, {
errorCode: ErrorCode.FailedToGetRecordDataStructure,
});
}
};
/**
* Searchs for available tasks.
* @alias searchTasks
* @memberof API.RAPID
* @param {string} [filter] Only symbols in the string pattern.
* @param {boolean} [caseSensitive] If the value is set to the default "false", non-case-sensitive is applied for the filter; otherwise, case-sensitive is applied.
* @returns {Promise<object>}
* @example
* await API.RAPID.searchTasks()
*/
this.searchTasks = async function (filter = '', caseSensitive = false) {
if (typeof filter !== 'string') {
throw new Error('The filter string should be a valid string.');
}
try {
const allTasks = await RWS.Rapid.getTasks();
const taskNames = allTasks.map((t) => t.getName());
const flags = caseSensitive ? '' : 'i';
const regex = new RegExp(filter, flags);
const filteredTaskNames = taskNames.filter((element) => regex.test(element));
return taskNames.filter((element) => regex.test(element));
} catch (error) {
return API.rejectWithStatus('Failed to get tasks', error, {
errorCode: ErrorCode.FailedToSearchTasks,
});
}
};
/**
* Monitors the state change of the RAPID program execution. It is possible to call a callback function every time the state is changed.
* Current state is stored in {@link executionState} variable. Additionally, {@link isRunning}
* is updated correspondingly.
* @alias monitorExecutionState
* @memberof API.RAPID
* @param {function} [callback] The callback function that is called when the execution state changes.
* @example
* // The callback will be executed when execution status is changed
* const execStatus = (value)=>{console.log("Execution status is:",value);}
* await API.RAPID.monitorExecutionState(execStatus);
*/
this.monitorExecutionState = async function (callback = null) {
if (this.executionState === undefined) {
this.executionState = await RWS.Rapid.getExecutionState();
this.isRunning = this.executionState === RWS.Rapid.ExecutionStates.running ? true : false;
const cbExecState = function (data) {
this.executionState = data;
data === RWS.Rapid.ExecutionStates.running ? (this.isRunning = true) : (this.isRunning = false);
API._events.trigger('execution', data);
Logger.i(InfoType.RobotOperation, `Controller Execution status is changed to: ${this.executionState}`);
};
await subscribeRes(RWS.Rapid.MonitorResources.execution, cbExecState.bind(this));
callback(this.executionState);
}
API._events.on('execution', callback);
};
/**
* Sets program pointer to main
* @note SPOC systems (e.g., RobotWare 8): Write access must be held before setting program pointer. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
* @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as edit mastership is handled internally.
* @alias setPPToMain
* @memberof API.RAPID
* @example
* await API.RAPID.setPPToMain()
*/
this.setPPToMain = async function () {
try {
await API.RWS.requestMastership('edit');
try {
await RWS.Rapid.resetPP();
} catch (error) {
return API.rejectWithStatus('Failed to reset RAPID program pointer', error, {
errorCode: ErrorCode.FailedToResetPP,
});
}
} finally {
await API.RWS.releaseMastership('edit');
}
};
/**
* Sets program pointer to routine
* @note SPOC systems (e.g., RobotWare 8): Write access must be held before setting program pointer. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
* @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as edit mastership is handled internally.
* @alias setPPToRoutine
* @memberof API.RAPID
* @example
* await API.RAPID.setPPToRoutine("MainModule","proc1")
*/
this.setPPToRoutine = async function (moduleName, routineName, taskName = 'T_ROB1') {
if (!(moduleName && routineName)) return;
try {
await API.RWS.requestMastership('edit');
try {
await API.RWS.RAPID.setPPToRoutine(moduleName, routineName, taskName);
} catch (error) {
return API.rejectWithStatus('Failed to set program pointer to routine', error, {
errorCode: ErrorCode.FailedToSetPPToRoutine,
});
}
} finally {
await API.RWS.releaseMastership('edit');
}
};
/**
* Gets the program pointer
* @alias getProgramPointer
* @memberof API.RAPID
* @returns {Promise<objecy>} A promise with an object containing the program pointer information.
* @example
* await API.RAPID.getProgramPointer()
*/
this.getProgramPointer = async function (taskName = 'T_ROB1') {
try {
let pcpInfo = await API.RWS.RAPID.getPointersInfo(taskName);
let programPointer = pcpInfo[0];
let beginPos =
programPointer['beginposition'] != undefined
? programPointer['beginposition'].split(',')
: [undefined, undefined];
let endPos =
programPointer['endposition'] != undefined
? programPointer['endposition'].split(',')
: [undefined, undefined];
let moduleName =
programPointer['modulemame'] != undefined ? programPointer['modulemame'] : programPointer['modulename'];
return {
moduleName: moduleName,
routineName: programPointer['routinename'],
BegPosLine: beginPos[0],
BegPosCol: beginPos[1],
EndPosLine: endPos[0],
EndPosCol: endPos[1],
};
} catch (error) {
return API.rejectWithStatus('Get program pointer failed', error, {
errorCode: ErrorCode.FailedToGetProgramPointer,
});
}
};
// this.monitorMechUnit = async function (callback = null) {
// try {
// if (typeof callback !== 'function' && callback !== null)
// throw new Error('callback is not a valid function');
// const cbMechUnit = function (data) {
// this.mechUnit = data;
// callback && callback(data);
// };
// subscribeRes('mechunit', cbMechUnit.bind(this));
// } catch (e) {
// return API.rejectWithStatus('Failed to subscribe execution state', e);
// }
// };
/**
* @typedef startExecutionProps
* @prop {string} regainMode - valid values: 'continue', 'regain', 'clear' or 'enter_consume'
* @prop {string} execMode - valid values: 'continue', 'step_in', 'step_over', 'step_out', 'step_backwards', 'step_to_last' or 'step_to_motion'
* @prop {string} cycleMode - valid values: 'forever', 'as_is' or 'once'
* @prop {string} condition - valid values: 'none' or 'call_chain'
* @prop {boolean} stopAtBreakpoint - stop at breakpoint
* @prop {boolean} enableByTSP - all tasks according to task selection panel
* @private
*/
/**
* This class represents a RAPID Task. It includes some usefull ready-to-use functionalities based on the the OmniCore SDK.
* This class cannot be directly instantiated. Therefore the {@link API.RAPID.getTask()}
* method instead.
* @class Task
* @memberof API.RAPID
* @param {object} task A RWS.RAPID task object.
* @example
* const task = await API.RAPID.getTask('T_ROB1');
* const modules = await task.searchModules();
* @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
*/
class Task {
constructor(task) {
this._task = task;
this.name = task.getName();
}
/**
* Load a module from the controller HOME files
* @alias loadModule
* @memberof API.RAPID.Task
* @param {string} path Path to the module file in
* the HOME directory (included extension of the module).
* @param {boolean} replace If true, it will replace an existing module in RAPID with the same name
* @note SPOC systems (e.g., RobotWare 8): Write access must be held before loading module. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
* @note Non-SPOC systems (e.g., RobotWare 7): Edit mastership is handled internally by this interface.
* @example
* let url = `${this.path}/${this.name}${this.extension}`;
* await task.loadModule(url, true);
*/
async loadModule(path, replace = false) {
return await loadModule(path, replace, this.name);
}
/**
* Unloads a RAPID module
* @alias unloadModule
* @memberof API.RAPID.Task
* @param {string} module Module's name
* @example
* await task.unloadModule("MainModule");
*/
async unloadModule(module) {
return await unloadModule(module, this.name);
}
/**
* @typedef stopExecutionProps
* @prop {RWS.Rapid.StopModes} [stopMode] Stop mode, valid values: 'cycle', 'instruction', 'stop' or 'quick_stop'
* @prop {RWS.Rapid.UseTSPOptions.normal} [useTSP] Use task selection panel, valid values: 'normal' or 'all_tasks'
* @memberof API.RAPID.Task
*/
/**
* Stops the RAPID execution with the settings given in the parameter object. All or any of the defined parameters can be supplied, if a value is omitted a default value will be used. The default values are:
* stopMode = 'stop'
* useTSP = 'normal'
* @alias stopExecution
* @memberof API.RAPID.Task
* @param {stopExecutionProps} props
* @deprecated since version 1.1.0, use API.RAPID.stopExecution instead
* @example
* const task = await API.RAPID.getTask('T_ROB1');
* await task.stopExecution();
*/
async stopExecution({stopMode = RWS.Rapid.StopModes.stop, useTSP = RWS.Rapid.UseTSPOptions.normal} = {}) {
var state = await RWS.Rapid.getExecutionState();
if (state === RWS.Rapid.ExecutionStates.running) {
try {
await RWS.Rapid.stopExecution({
stopMode,
useTSP,
});
} catch (e) {
return API.rejectWithStatus('Failed to stop program execution', e, {
errorCode: ErrorCode.FailedToStopProgramExecution,
});
}
}
}
/**
* @typedef { 'constant' | 'variable' | 'persistent'} VariableSymbolType ;
* @memberof API.RAPID
*/
/**
* @typedef filterVariables
* @prop {string} [name] Name of the data symbol (not case-sensitive)
* @prop {VariableSymbolType} [symbolType] Valid values: 'constant', 'variable', 'persistent' (array with multiple values is supported)
* @prop {string} [dataType] Type of the data, e.g. 'num'(only one data type is supported)
* i.e. a type declaration that has no declared variable will not be returned when this flag is set true.
* @memberof API.RAPID
*/
/**
* Searchs for variables contained in a module
* @alias searchVariables
* @memberof API.RAPID.Task
* @param {string} module - Module where the search takes place
* @param {boolean} [isInUse] Only return symbols that are used in a Rapid program,
* @param {filterVariables} [filter] See {@link filterVariables}
* @returns {Promise<RWS.Rapid.SymbolSearchResult[]>}
* @example
* // list all num variables in MainModule of task T_ROB1
* const task = await API.RAPID.getTask('T_ROB1');
* await task.searchVariables("MainModule", false, {dataType: "num"});
*/
async searchVariables(module = null, isInUse = false, filter = {}) {
let searchFilter = {module, isInUse};
let types;
if (Object.prototype.hasOwnProperty.call(filter, 'symbolType')) {
types = Array.isArray(filter.symbolType)
? filter.symbolType.reduce((all, entry, idx, arr) => {
if (entry === 'rapidData') {
arr.splice(1); // eject early
return RWS.Rapid.SymbolTypes[entry];
}
return entry === 'constant' || entry === 'variable' || entry === 'persistent'
? all + RWS.Rapid.SymbolTypes[entry]
: all;
}, 0)
: RWS.Rapid.SymbolTypes[filter.symbolType];
} else {
types = RWS.Rapid.SymbolTypes['rapidData:'];
}
if (types !== undefined) {
searchFilter.symbolType = types;
}
searchFilter.task = this.name;
if (Object.prototype.hasOwnProperty.call(filter, 'name')) searchFilter.name = filter.name;
if (Object.prototype.hasOwnProperty.call(filter, 'dataType')) searchFilter.dataType = filter.dataType;
return searchSymbol(searchFilter);
}
/**
* Searchs for available modules
* @alias searchModules
* @memberof API.RAPID.Task
* @param {boolean} [isInUse] Only return symbols that are used in a RAPID program,
* i.e. a type declaration that has no declared variable will not be returned when this flag is set to true.
* @param {string} [filter] Only symbols in the string pattern (not case-sensitive)
* @returns {Promise<RWS.Rapid.SymbolSearchResult[]>}
* @example
* const task = await API.RAPID.getTask('T_ROB1');
* await task.searchModules()
*/
async searchModules(isInUse = false, filter = '') {
return searchSymbol({
task: this.name,
isInUse: isInUse,
symbolType: RWS.Rapid.SymbolTypes.module,
name: filter,
});
}
/**
* Searchs for procedures contained in a module
* @alias searchProcedures
* @memberof API.RAPID.Task
* @param {string} module Module where the search takes place
* @param {boolean} [isInUse] Only return symbols that are used in a RAPID program, i.e. a type declaration that has no declared variable will not be returned when this flag is set true.
* @param {string} [filter] Only symbols in the string pattern (not case-sensitive)
* @returns {Promise<RWS.Rapid.SymbolSearchResult[]>}
* @example
* const task = await API.RAPID.getTask('T_ROB1');
* await task.searchProcedures()
*/
async searchProcedures(module = null, isInUse = false, filter = '') {
return searchSymbol({
task: this.name,
module: module,
isInUse: isInUse,
symbolType: RWS.Rapid.SymbolTypes.procedure,
name: filter,
});
}
/**
* @typedef { 'procedure' | 'function' | 'trap'} RoutineSymbolType ;
* @memberof API.RAPID
*/
/**
* @typedef filterRoutines
* @prop {string} [name] Name of the data symbol (not case-sensitive)
* @prop {RoutineSymbolType} [symbolType] Valid values: 'procedure', 'function', 'trap' , 'routine'(array with multiple values is supported)
* @memberof API.RAPID
*/
/**
* Search for routines contained in a module
* @alias searchRoutines
* @memberof API.RAPID.Task
* @param {string} module Module where the search takes place
* @param {boolean} [isInUse] Only return symbols that are used in a Rapid program,
* @param {filterRoutines} [filter] See {@link filterRoutines}
*
* @returns {Promise<RWS.Rapid.SymbolSearchResult[]>}
* @example
* const task = await API.RAPID.getTask('T_ROB1');
* await task.searchRoutines()
*/
async searchRoutines(module = null, isInUse = false, filter = {}) {
let types;
if (Object.prototype.hasOwnProperty.call(filter, 'symbolType')) {
types = Array.isArray(filter.symbolType)
? filter.symbolType.reduce((all, entry, idx, arr) => {
if (entry === 'routine') {
arr.splice(1); // eject early
return RWS.Rapid.SymbolTypes[entry];
}
return entry === 'procedure' || entry === 'function' || entry === 'trap'
? all + RWS.Rapid.SymbolTypes[entry]
: all;
}, 0)
: RWS.Rapid.SymbolTypes[filter.symbolType];
} else {
types = RWS.Rapid.SymbolTypes['routine'];
}
return searchSymbol({
task: this.name,
module: module,
isInUse: isInUse,
symbolType: types,
name: filter.name,
});
}
/**
* Gets the value of a RAPID variable.
* @alias getValue
* @memberof API.RAPID.Task
* @param {string} module The module containing the variable.
* @param {string} variable The variable name
* @returns {Promise<object>}
* @example
* // get the variable value of RAPID variable n1 in MainModule
* const task = await API.RAPID.getTask('T_ROB1');
* await task.getValue("MainModule", "n1")
*/
async getValue(module, variable) {
try {
var data = await this._task.getData(module, variable);
// const properties = await data.getProperties();
return await data.getValue();
} catch (e) {
return API.rejectWithStatus(
`Variable ${variable} not found at ${this._task.getName()} : ${module} module.`,
e,
);
}
}
/**
* Sets the value of a variable
* @alias setValue
* @memberof API.RAPID.Task
* @param {string} module Module containing the variable
* @param {string} variable Variable name
* @param {object} value Value of the variable
* @returns {Promise<object>}
* @todo Valiation of value not yet applied
* @example
* // set the variable value of RAPID variable n1 in MainModule
* const task = await API.RAPID.getTask('T_ROB1');
* await task.setValue("MainModule", "aa",7);
*/
async setValue(module, variable, value) {
try {
var data = await this._task.getData(module, variable);
await data.setValue(value);
} catch (err) {
return API.rejectWithStatus(`Failed to set variable ${module}:${variable}.`, err, {
errorCode: ErrorCode.FailedToSetVariableValue,
msgParams: {name: variable, value},
});
}
}
/**
* Gets a RWS Data object variable
* @alias getVariableInstance
* @memberof API.RAPID.Task
* @param {string} module The module containing the variable
* @param {string} variable The variable name
* @param {boolean} subscribe
* @returns {Promise<object>} API.RAPID.Variable object
* @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
* @example
* const task = await API.RAPID.getTask('T_ROB1');
* await task.getVariableInstance("MainModule", "aa")
*/
async getVariable(module, variable, subscribe = true) {
return await getVariableInstance(this.name, module, variable, subscribe);
}
/**
* Gets a module. This will retrieve the properties of the module from the controller and initialize the object.
* @alias getModule
* @memberof API.RAPID.Task
* @param {object} module - The name of the module
* @returns {Promise<object>} A RWS module object.
* @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
* @example
* const task = await API.RAPID.getTask('T_ROB1');
* await task.getModule("MainModule")
*/
async getModule(module) {
return await this._task.getModule(module);
}
}
/**
* @typedef VariableProps
* @prop {string} [taskName] The task name
* @prop {string} [moduleName] The module name
* @prop {string} [symbolName] The symbol name
* @prop {string} [dataType] The symbol's data type
* @prop {string} [symbolType] The declaration type of the data, valid values:
* 'constant'
* 'variable'
* 'persistent'
* @prop {number[]} dimensions List of dimensions for arrays
* @prop {string} [scope] The data's scope, valid values:
* 'local'
* 'task'
* 'global'
* @prop {string} [dataTypeURL] RWS URL to the data’s type symbol
* @memberof API.RAPID
*/
/**
* This class represents a RAPID variable.
* @class Variable
* @memberof API.RAPID
* @param {RWS.Rapid.Data} variable
* @param {VariableProps} props See {@link VariableProps}
* @property {object} var Object returned by RWS.Rapid.getData().
* @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
*/
class Variable extends API.Events {
constructor(variable, props) {
super();
this.props = props;
this.var = variable;
this.callbacks = [];
this._subscribed = false;
}
get name() {
return this.props.symbolName;
}
/**
* @deprecated avoid providing an async getter as it can cause unexpected behavior. Use getValue() instead.
*/
get value() {
return (async () => {
try {
await this.var.fetch();
return await this.var.getValue();
} catch (e) {
return API.rejectWithStatus(`Failed to get value of variable "${this.name}"`, e, {
errorCode: ErrorCode.FailedToGetVariableValue,
});
}
})();
}
set value(v) {
this.var && this.var.setValue(v);
}
async getValue() {
try {
// await this.var.fetch();
return await this.var.getValue();
} catch (e) {
return API.rejectWithStatus(`Failed to get value of variable "${this.name}"`, e, {
errorCode: ErrorCode.FailedToGetVariableValue,
});
}
}
async setValue(v) {
try {
if (this.type === 'num' && typeof v === 'string') v = Number.parseInt(v);
return this.var && (await this.var.setValue(v));
} catch (e) {
return API.rejectWithStatus(`Failed to set value of variable ${this.name}`, e, {
errorCode: ErrorCode.FailedToSetVariableValue,
msgParams: {name: this.name, value: v},
});
}
}
async setRawValue(v) {
try {
if (typeof v != 'string') throw new Error('Incorrect raw value');
return this.var && (await this.var.setRawValue(v));
} catch (e) {
return API.rejectWithStatus(`Failed to set value of variable ${this.name}`, e, {
errorCode: ErrorCode.FailedToSetVariableValue,
msgParams: {name: this.name, value: v},
});
}
}
async getRawValue() {
try {
// await this.var.fetch();
return await this.var.getRawValue();
} catch (e) {
return API.rejectWithStatus(`Failed to get value of variable "${this.name}"`, e, {
errorCode: ErrorCode.FailedToGetVariableValue,
});
}
}
get type() {
return this.props.dataType;
}
/**
* Returns the declaration type of the data, valid values:
* 'constant'
* 'variable'
* 'persistent'
*/
get symbolType() {
return this.props.symbolType;
}
/**
* Subscribes to a RAPID variable
* @alias subscribe
* @param {boolean} raiseInitial Raises an event after subscription
* @returns {undefined | Promise<{}>} Undefined at success, reject Promise at failure.
* @memberof API.RAPID.Variable
*/
async subscribe(raiseInitial = false) {
try {
await this._onChanged();
if (!this._subscrided) {
await this.var.subscribe(raiseInitial);
this._subscrided = true;
}
} catch (e) {
return API.rejectWithStatus(`Failed to subscribe variable "${this.name}"`, e, {
errorCode: ErrorCode.FailedToSubscribeVariable,
});
}
}
/**
* Unsubscribes a RAPID variable
* @alias unsubscribe
* @returns {undefined | Promise<{}>} Undefined at success, reject Promise at failure.
* @memberof API.RAPID.Variable
*/
async unsubscribe() {
if (this._subscrided) {
try {
this._subscribed = false;
return API.RAPID.unsubscribeVariable(this.props.taskName, this.props.moduleName, this.name);
} catch (e) {
return API.rejectWithStatus(`Failed to unsubscribe variable "${this.name}"`, e, {
errorCode: ErrorCode.FailedToUnsubscribeResource,
});
}
}
}
/**
* Internal callback for variable specific handling. This method is called inside the subscribe method
* @returns {undefined | Promise<{}>} undefined at success, reject Promise at fail.
* @private
*/
async _onChanged() {
try {
const cb = async (value) => {
if (value === undefined) {
value = await this.var.getValue();
}
this.trigger('changed', this._adapter(value));
};
this.var.addCallbackOnChanged(cb.bind(this));
} catch (e) {
return API.rejectWithStatus(`Failed to add callback on changed for "${this.name}"`, e, {
errorCode: ErrorCode.FailedToAddCallbackOnChanged,
});
}
}
_adapter(value) {
return value;
}
/**
* Add a callback function to be executed when the variable value changes
* @alias onChanged
* @param {*} callback The callback function that is called when the variable value changes.
* @memberof API.RAPID.Variable
*/
onChanged(callback) {
this.on('changed', callback);
}
}
/**
* This class represents a RAPID variable in "string" type.
* @class VariableString
* @extends API.RAPID.Variable
* @memberof API.RAPID
* @param {RWS.Rapid.Data} variable
* @param {VariableProps} props See {@link VariableProps}
* @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
*/
class VariableString extends Variable {
async getValue() {
const value = await super.getValue();
return value;
}
async setValue(value) {
await super.setValue(value);
}
_adapter(value) {
return value.replace(/"$/, '').replace(/^"/, '');
}
}
/**
* This class represents a RAPID variable in "bool" type.
* @class VariableBool
* @extends API.RAPID.Variable
* @memberof API.RAPID
* @param {RWS.Rapid.Data} variable
* @param {VariableProps} props See {@link VariableProps}
* @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
*/
class VariableBool extends Variable {
async getValue() {
const value = await super.getValue();
return value;
}
async setValue(value) {
await super.setValue(value);
}
_adapter(value) {
if (typeof value == 'string') {
return JSON.parse(
value.replace(/TRUE/g, 'true').replace(/FALSE/g, 'false').replace(/"$/, '').replace(/^"/, ''),
);
}
return value;
}
}
/**
* This class represents a RAPID variable in "num" or "dnum" type.
* @class VariableNum
* @extends API.RAPID.Variable
* @memberof API.RAPID
* @param {RWS.Rapid.Data} variable
* @param {VariableProps} props See {@link VariableProps}
* @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
*/
class VariableNum extends Variable {
async getValue() {
const value = await super.getValue();
return this._getArrayItems(value);
}
async setValue(value) {
const setArrayItems = async function (array, variable, indices = []) {
array.forEach(async (element, index) => {
if (Array.isArray(element)) {
await setArrayItems(element, variable, [...indices, index + 1]);
} else {
await variable.setArrayItem(Number(element), ...[...indices, index + 1]);
}
});
};
if (typeof value === 'string') value = JSON.parse(value);
if (Array.isArray(value)) {
await setArrayItems(value, this.var);
} else {
await super.setValue(Number(value));
}
}
_adapter(value) {
return this._getArrayItems(value);
}
_getArrayItems(value) {
if (typeof value === 'string') value = JSON.parse(value);
if (Array.isArray(value)) {
const ret = value.map((v) => this._getArrayItems(v));
return ret;
} else {
return Number(value);
}
}
}
/**
* This class represents a RAPID variable in "robtarget" type.
* @class VariableRobTarget
* @extends API.RAPID.Variable
* @memberof API.RAPID
* @param {RWS.Rapid.Data} variable
* @param {VariableProps} props See {@link VariableProps}
* @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
*/
class VariableRobTarget extends Variable {
async getValue() {
const value = await super.getValue();
// const parsedRobTarget = API.RWS.MOTIONSYSTEM.parseRobTarget(value);
// return parsedRobTarget;
return value;
}
async setValue(value) {
await super.setValue(value);
}
_adapter(value) {
return value;
}
}
/**
* This class represents a RAPID variable in "jointtarget" type.
* @class VariableJointTarget
* @extends API.RAPID.Variable
* @memberof API.RAPID
* @param {RWS.Rapid.Data} variable
* @param {VariableProps} props See {@link VariableProps}
* @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-SDK}
*/
class VariableJointTarget extends Variable {
async getValue() {
const value = await super.getValue();
// const parsedJointTarget = API.RWS.MOTIONSYSTEM.parseJointTarget(value);
// return parsedJointTarget;
return value;
}
async setValue(value) {
await super.setValue(value);
}
_adapter(value) {
return value;
}
}
const AtomicRapidSymbolTypeList = ['num', 'dnum', 'string', 'bool', 'byte'];
const getFirstLevelComponents = function (rapid) {
const result = [];
let current = '';
let level = 0;
rapid = rapid.slice(1, -1);
try {
for (let i = 0; i < rapid.length; i++) {
const char = rapid[i];
if (char === '[') {
if (level === 0 && current.trim() !== '') {
result.push(current.trim());
current = '';
}
level++;
current += char;
} else if (char === ']') {
level--;
current += char;
if (level === 0) {
result.push(current.trim());
current = '';
}
} else if (char === ',' && level === 0) {
if (current.trim() !== '') {
result.push(current.trim());
}
current = '';
} else {
current += char;
}
}
if (current) {
result.push(current);
}
return result;
} catch (e) {
return API.rejectWithStatus('Failed to parse RAPID array or RECORD data', e, {
errorCode: ErrorCode.FailedToParseRapidData,
});
}
};
this.parseRapidArrayValue = function (rapid, dimension) {
try {
let result = rapid;
if (!rapid || typeof rapid !== 'string') throw new Error('Incorrect rapid value');
if (dimension.length > 3) throw new Error('RAPID does not support array with dimension more than 3');
for (let i = 0; i < dimension.length; i++) {
if (dimension[i] < 0) throw new Error('Incorrect dimension number');
let firstLevel = getFirstLevelComponents(result);
if (firstLevel.length == 0 || firstLevel.length < dimension[i] + 1) throw new Error('Incorrect array format');
result = firstLevel[dimension[i]];
}
return result;
} catch (e) {
return API.rejectWithStatus('Failed to parse rapid array value', e, {
errorCode: ErrorCode.FailedToParseRapidData,
});
}
};
this.generateRapidArrayValue = function (v, rawValue, dimension) {
try {
if (!rawValue || typeof rawValue !== 'string') {
throw new Error('Incorrect raw value');
}
if (!dimension || dimension.length === 0) {
throw new Error('Dimension cannot be empty');
}
if (dimension.length > 3) {
throw new Error('RAPID does not support array with dimension more than 3');
}
for (let i = 0; i < dimension.length; i++) {
if (dimension[i] < 0) {
throw new Error('Incorrect dimension number');
}
}
const replaceAtDimension = (currentValue, dims, newValue, currentDim = 0) => {
const levels = getFirstLevelComponents(currentValue);
if (currentDim === dims.length - 1) {
if (dims[currentDim] >= levels.length) {
throw new Error('Dimension index out of bounds');
}
if (levels[dims[currentDim]].startsWith('"') && levels[dims[currentDim]].endsWith('"')) {
newValue = `"${newValue}"`;
}
levels[dims[currentDim]] = newValue;
return `[${levels.join(',')}]`;
} else {
if (dims[currentDim] >= levels.length) {
throw new Error('Dimension index out of bounds');
}
const updatedLevel = replaceAtDimension(levels[dims[currentDim]], dims, newValue, currentDim + 1);
levels[dims[currentDim]] = updatedLevel;
return `[${levels.join(',')}]`;
}
};
return replaceAtDimension(rawValue, dimension, v);
} catch (e) {
return API.rejectWithStatus('Failed to generate array value', e, {
errorCode: ErrorCode.FailedToGenerateRapidArray,
});
}
};
/**
* This class represents a RAPID RECORD data type.
* This class includes some necessary properties to modelize RAPID RECORD and methods to handle RAPID RECORD, such as the methods to parse RECORD's data structure.
* @class RecordData
* @extends API.RAPID
* @memberof API.RAPID
* @param {string} type RECORD type
* @param {string} name RECORD component name
* @param {string} blockUrl The URL to the source RECORD.
* @param {boolean} isAtom True if the RECORD is one of the RAPID atomic data types: num, bool, string, dnum, byte
* @param {array<API.RAPID.RecordData>} children The child parts owned by the RECORD
* @param {boolean} isUnknownAtom True if the RECORD is in atomic type that does not included by any known ones.
* @param {any} value The value of the RECORD data
*/
class RecordData {
constructor(type, blockUrl, children = [], name = '', value = null) {
this.type = type;
this.blockUrl = blockUrl;
this.children = children;
this.isAtom = AtomicRapidSymbolTypeList.includes(this.type);
this.name = name;
this.setValue(value);
}
addChild(child) {
this.children.push(child);
}
fromJSON(json) {
const data = typeof json === 'string' ? JSON.parse(json) : json;
const instance = new RecordData(
data.type,
data.blockUrl,
data.children.map((child) => this.fromJSON(child)),
data.name,
data.value,
);
return instance;
}
setValue(value) {
if (this.isAtom) {
switch (this.type) {
case 'num':
case 'dnum':
this.value = typeof value != 'undefined' ? value : 0;
break;
case 'string':
this.value = typeof value != 'undefined' ? value : '""';
break;
case 'bool':
this.value = typeof value != 'undefined' ? value : false;
break;
default:
// 1. unknown RAPID value-type built-in atomic types
// 2. atomic type from reaching max recursive depth
this.value = typeof value != 'undefined' ? value : this.isUnknownAtom ? '' : '[]';
break;
}
} else if (value) {
this.value = value;
}
}
getRapidValue() {
if (this.isAtom) {
if (this.type == 'string') {
if (this.value.startsWith('"') && this.value.endsWith('"')) {
return this.value;
} else {
return `"${this.value}"`;
}
}
return `${this.value}`;
}
let rapidValue = '';
for (let i = 0; i < this.children.length; i++) {
rapidValue += (i == 0 ? '' : ',') + this.children[i].getRapidValue();
}
this.value = '[' + rapidValue + ']';
return this.value;
}
parseFromRapid(rapid) {
if (!rapid) return;
this.setValue(rapid);
if (this.children.length > 0) {
const firstLevelComponents = getFirstLevelComponents(rapid);
if (!firstLevelComponents || firstLevelComponents.length !== this.children.length) {
return API.rejectWithStatus(
'parse record data failed',
{},
{
errorCode: ErrorCode.FailedToParseRapidData,
},
);
}
for (let i = 0; i < firstLevelComponents.length; i++) {
this.children[i].parseFromRapid(firstLevelComponents[i]);
}
}
}
setValueByNamePath(namePath, value) {
if (!namePath || namePath.length == 0) {
this.value = value;
return;
}
let current = this;
for (let i = 0; i < namePath.length; i++) {
const name = namePath[i];
if (!current.children || !current.children.length) throw new Error('Cannot set value, value path is invalid');
const child = current.children.find((c) => c.name === name);
if (!child) throw new Error('Cannot set value, value path is invalid');
if (i === namePath.length - 1) {
child.value = value;
}
current = child;
}
}
getValueByNamePath(namePath) {
if (!namePath || namePath.length == 0) return this.value;
let current = this;
for (let i = 0; i < namePath.length; i++) {
const name = namePath[i];
if (!current.children || !current.children.length) return undefined;
const child = current.children.find((c) => c.name === name);
if (!child) return undefined;
if (i === namePath.length - 1) {
return child.value;
}
current = child;
}
return undefined;
}
getChildrenByNamePath(namePath) {
if (!namePath.length) return undefined;
let current = this;
for (let i = 0; i < namePath.length; i++) {
const name = namePath[i];
if (!current.children || !current.children.length) return undefined;
const child = current.children.find((c) => c.name === name);
if (!child) return undefined;
if (i === namePath.length - 1) {
return child;
}
current = child;
}
return undefined;
}
}
this.getRecordData = function (type, blockUrl, children = [], name = '', value = null) {
return new RecordData(type, blockUrl, children, name, value);
};
const ReservedDataTypes = {
POS: 'pos',
ORIENT: 'orient',
EXTJOINT: 'extjoint',
ROBJOINT: 'robjoint',
POSE: 'pose',
JOINTTARGET: 'jointtarget',
CONFDATA: 'confdata',
ROBTARGET: 'robtarget',
LOADDATA: 'loaddata',
TOOLDATA: 'tooldata',
WOBJDATA: 'wobjdata',
ZONEDATA: 'zonedata',
SPEEDDATA: 'speeddata',
};
const AtomicRapidSymbolTypes = {
NUM: 'num',
DNUM: 'dnum',
STR: 'string',
BOOL: 'bool',
BYTE: 'byte',
};
const SymbolValueTypes = {
SemiValue: 'semival',
Value: 'val',
NoValue: 'nonval',
};
const MaxDepth = 3;
this.getRecordDataStructure = async function (type, typeUrl = '', name = '', currentDepth = 0, task = 'T_ROB1') {
let exturl = 'symbols/search';
let blockurl = typeUrl ? typeUrl : `RAPID/${task}/` + type;
let searchFlag = false;
let hasError = false;
let dataStruct = new RecordData(type, blockurl);
dataStruct.name = name;
if (AtomicRapidSymbolTypeList.includes(type)) {
dataStruct.isAtom = true;
dataStruct.isUnknownAtom = false;
} else if (Object.values(ReservedDataTypes).includes(type)) {
// check if reserved data type: reduce RWS requests
let reservedStruc = getDataStructure(type);
if (!reservedStruc) throw new Error('get reserved data structure failed');
dataStruct.isAtom = reservedStruc.isAtom;
dataStruct.type = reservedStruc.type;
dataStruct.blockUrl = reservedStruc.blockUrl;
dataStruct.children = reservedStruc.children;
dataStruct.isUnknownAtom = reservedStruc.isUnknownAtom;
} else {
try {
// check if recursive depth overreaches the max depth
if (currentDepth > MaxDepth) {
throw new Error('The nested level of custom data structure reaches the Max level');
} else {
currentDepth = currentDepth + 1;
dataStruct.isAtom = false;
// check if custom data is value type: non-value or semi-value type cannot be edited
let property;
try {
property = await API.RWS.RAPID.getDataTypeProperty(blockurl);
} catch (error) {
// check if RAPID built-in type
blockurl = 'RAPID/' + type;
property = await API.RWS.RAPID.getDataTypeProperty(blockurl);
dataStruct.blockUrl = blockurl;
}
if (!equalsIgnoreCase(property.valtyp, SymbolValueTypes.Value)) throw new Error('Not value type');
// check if unknown RAPID value-type atomic type: except num,dnum,string,bool
if (property.symtyp && equalsIgnoreCase(property.symtyp, 'atm')) {
dataStruct.isAtom = true;
dataStruct.type = type;
dataStruct.blockUrl = blockurl;
dataStruct.children = [];
dataStruct.isUnknownAtom = true;
} else {
while (!searchFlag) {
let searchedResult = await API.RWS.RAPID.getDataTypeStructure(blockurl, exturl);
let symbols = searchedResult['_embedded']['resources'];
// The order of components in the RWS response is exactly the reverse of the actual component order
for (let i = symbols.length - 1; i >= 0; i--) {
let child = await this.getRecordDataStructure(
symbols[i].dattyp,
symbols[i].typurl,
symbols[i].name,
currentDepth,
task,
);
if (!child) throw new Error('get custom data structure failed');
dataStruct.addChild(child);
}
if (typeof searchedResult._links.next == 'undefined') {
searchFlag = true;
} else {
exturl = searchedResult._links.next.href;
}
}
}
}
} catch (error) {
searchFlag = true;
hasError = true;
Logger.e(logModule, ErrorCode.FailedToGetRecordDataStructure, error);
// throw 'get custom data structure failed'
}
}
return hasError ? null : dataStruct;
};
const equalsIgnoreCase = (textA, textB) => {
if (textA && textB) {
return textA.toLowerCase() == textB.toLowerCase();
}
return false;
};
const getDataStructure = function (type) {
let dataStruct;
switch (type) {
case ReservedDataTypes.POS:
dataStruct = generatePosDataStructure();
break;
case ReservedDataTypes.ORIENT:
dataStruct = generateOrientDataStructure();
break;
case ReservedDataTypes.EXTJOINT:
dataStruct = generateExtjointDataStructure();
break;
case ReservedDataTypes.ROBJOINT:
dataStruct = generateRobjointDataStructure();
break;
case ReservedDataTypes.POSE:
dataStruct = generatePoseDataStructure();
break;
case ReservedDataTypes.JOINTTARGET:
dataStruct = {
type: ReservedDataTypes.JOINTTARGET,
name: '',
blockUrl: 'RAPID/jointtarget',
isAtom: false,
children: [generateRobjointDataStructure('robax'), generateExtjointDataStructure('extax')],
isUnknownAtom: false,
};
break;
case ReservedDataTypes.CONFDATA:
dataStruct = generateConfDataStructure();
break;
case ReservedDataTypes.ROBTARGET:
dataStruct = {
type: ReservedDataTypes.ROBTARGET,
name: '',
blockUrl: 'RAPID/robtarget',
isAtom: false,
children: [
generatePosDataStructure('trans'),
generateOrientDataStructure('rot'),
generateConfDataStructure('robconf'),
generateExtjointDataStructure('extax'),
],
isUnknownAtom: false,
};
break;
case ReservedDataTypes.LOADDATA:
dataStruct = generateLoadDataStructure();
break;
case ReservedDataTypes.TOOLDATA:
dataStruct = {
type: ReservedDataTypes.TOOLDATA,
name: '',
blockUrl: 'RAPID/tooldata',
isAtom: false,
children: [
generateCommonDataStructure(AtomicRapidSymbolTypes.BOOL, 'robhold'),
generatePoseDataStructure('tframe'),
generateLoadDataStructure('tload'),
],
isUnknownAtom: false,
};
break;
case ReservedDataTypes.WOBJDATA:
dataStruct = {
type: ReservedDataTypes.WOBJDATA,
name: '',
blockUrl: 'RAPID/wobjdata',
isAtom: false,
children: [
generateCommonDataStructure(AtomicRapidSymbolTypes.BOOL, 'robhold'),
generateCommonDataStructure(AtomicRapidSymbolTypes.BOOL, 'ufprog'),
generateCommonDataStructure(AtomicRapidSymbolTypes.STR, 'ufmec'),
generatePoseDataStructure('uframe'),
generatePoseDataStructure('oframe'),
],
isUnknownAtom: false,
};
break;
case ReservedDataTypes.ZONEDATA:
dataStruct = {
type: ReservedDataTypes.ZONEDATA,
name: '',
blockUrl: 'RAPID/zonedata',
isAtom: false,
children: [
generateCommonDataStructure(AtomicRapidSymbolTypes.BOOL, 'finep'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'pzone_tcp'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'pzone_ori'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'pzone_eax'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'pzone_ori'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'pzone_leax'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'pzone_reax'),
],
isUnknownAtom: false,
};
break;
case ReservedDataTypes.SPEEDDATA:
dataStruct = {
type: ReservedDataTypes.SPEEDDATA,
name: '',
blockUrl: 'RAPID/speeddata',
isAtom: false,
children: [
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'v_tcp'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'v_ori'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'v_leax'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'v_reax'),
],
isUnknownAtom: false,
};
break;
default:
break;
}
return dataStruct;
};
this.getDataStructure = getDataStructure;
const generateCommonDataStructure = function (type, name) {
if (!AtomicRapidSymbolTypeList.includes(type)) return null;
return {
type: type,
name: name,
blockUrl: 'RAPID/' + type,
isAtom: true,
children: [],
isUnknownAtom: false,
};
};
const generatePosDataStructure = function (name = '') {
return {
type: ReservedDataTypes.POS,
name: name,
blockUrl: 'RAPID/pos',
isAtom: false,
children: [
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'x'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'y'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'z'),
],
isUnknownAtom: false,
};
};
const generateOrientDataStructure = function (name = '') {
return {
type: ReservedDataTypes.ORIENT,
name: name,
blockUrl: 'RAPID/orient',
isAtom: false,
children: [
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'q1'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'q2'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'q3'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'q4'),
],
isUnknownAtom: false,
};
};
const generatePoseDataStructure = function (name = '') {
return {
type: ReservedDataTypes.POSE,
name: name,
blockUrl: 'RAPID/pose',
isAtom: false,
children: [generatePosDataStructure('trans'), generateOrientDataStructure('rot')],
isUnknownAtom: false,
};
};
const generateExtjointDataStructure = function (name = '') {
return {
type: ReservedDataTypes.EXTJOINT,
name: name,
blockUrl: 'RAPID/extjoint',
isAtom: false,
children: [
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'eax_a'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'eax_b'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'eax_c'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'eax_d'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'eax_e'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'eax_f'),
],
isUnknownAtom: false,
};
};
const generateRobjointDataStructure = function (name = '') {
return {
type: ReservedDataTypes.ROBJOINT,
name: name,
blockUrl: 'RAPID/robjoint',
isAtom: false,
children: [
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'rax_1'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'rax_2'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'rax_3'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'rax_4'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'rax_5'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'rax_6'),
],
isUnknownAtom: false,
};
};
const generateConfDataStructure = function (name = '') {
return {
type: ReservedDataTypes.CONFDATA,
name: name,
blockUrl: 'RAPID/confdata',
isAtom: false,
children: [
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'cf1'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'cf4'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'cf6'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'cfx'),
],
isUnknownAtom: false,
};
};
const generateLoadDataStructure = function (name = '') {
return {
type: ReservedDataTypes.LOADDATA,
name: name,
blockUrl: 'RAPID/loaddata',
isAtom: false,
children: [
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'mass'),
generatePosDataStructure('cog'),
generateOrientDataStructure('aom'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'ix'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'iy'),
generateCommonDataStructure(AtomicRapidSymbolTypes.NUM, 'iz'),
],
isUnknownAtom: false,
};
};
/**
* Creates a variable instance.
* @alias createVariableInstance
* @memberof API.RAPID
* @param {string} module The name of the module where the variable locates..
* @param {string} name The variable name.
* @param {string} task The task name.
* @example
* let variableInstance = await API.RAPID.createVariableInstance("MainModule","aa");
*/
this.createVariableInstance = async function (module, name, task = 'T_ROB1') {
const instanceKey = `${task}_${module}_${name}`;
if (this.variableInstanceMap.has(instanceKey)) {
const instance = this.variableInstanceMap.get(instanceKey);
// The underlying `Data` class uses a cache;
// if no subscription is enabled, it will not automatically update the values,
// so you need to update them manually here.
await instance.var.fetch();
return instance;
}
try {
var v = await RWS.Rapid.getData(task, module, name);
} catch (error) {
return API.rejectWithStatus(`Failed to get the variable: ${name}`, error, {
errorCode: ErrorCode.FailedToGetBindedVariable,
});
}
const p = await v.getProperties();
let variable;
if (p.dataType === 'string') variable = new VariableString(v, p);
else if (p.dataType === 'bool') variable = new VariableBool(v, p);
else if (p.dataType === 'num' || p.dataType === 'dnum') variable = new VariableNum(v, p);
else if (p.dataType === 'robtarget') variable = new VariableRobTarget(v, p);
else if (p.dataType === 'jointtarget') variable = new VariableJointTarget(v, p);
else variable = new Variable(v, p);
this.variableInstanceMap.set(instanceKey, variable);
return variable;
};
/**
* Gets the data type of a RAPID variable
* @alias getVariableType
* @memberof API.RAPID
* @param {string} module The name of the module where the variable locates.
* @param {string} name The variable name
* @param {string} task The task name
* @example
* let type = await API.RAPID.getVariableType("MainModule","aa");
*/
this.getVariableType = async function (module, name, task = 'T_ROB1') {
try {
const v = await RWS.Rapid.getData(task, module, name);
const p = await v.getProperties();
return p.dataType;
} catch (error) {
return API.rejectWithStatus(`Failed to get variable ${name}'s type`, error, {
errorCode: ErrorCode.FailedToGetVariableProperty,
});
}
};
/**
* Gets the properties of a variable
* @alias getVariableProperties
* @memberof API.RAPID
* @param {string} module The name of the module where the variable locates.
* @param {string} name The variable name
* @param {string} task The task name
* @example
* let props = await API.RAPID.getVariableProperties("MainModule","aa");
*/
this.getVariableProperties = async function (module, name, task = 'T_ROB1') {
try {
const v = await RWS.Rapid.getData(task, module, name);
const p = await v.getProperties();
return p;
} catch (error) {
return API.rejectWithStatus(`Failed to get variable ${name}'s properties`, error, {
errorCode: ErrorCode.FailedToGetVariableProperty,
});
}
};
/**
* Get the value of a variable
* @alias getVariableValue
* @memberof API.RAPID
* @param {string} module The name of the module where the variable locates.
* @param {string} name The variable name
* @param {string} task The task name
* @example
* let value = await API.RAPID.getVariableValue("MainModule","aa");
*/
this.getVariableValue = async function (module, name, task = 'T_ROB1') {
try {
let variable = await API.RAPID.createVariableInstance(module, name, task);
let value = await variable.getValue();
return value;
} catch (e) {
return API.rejectWithStatus(`Failed to get variable ${name}'s value`, e, {
errorCode: ErrorCode.FailedToGetVariableValue,
});
}
};
/**
* @typedef setVariableValueOptions
* @prop {boolean} [syncPers=false] If the value is set to true, the variable value is synchronized immediately; otherwise, the variable value will be synchronized until next synchronization action; Set to false if RAPID program is running;
* @memberof API.RAPID
*/
/**
* Sets the value of a RAPID variable
* @alias setVariableValue
* @memberof API.RAPID
* @param {string} module The name of the module where the variable locates.
* @param {string} name The variable name
* @param {any} value The value to set
* @param {string} task The task name
* @param {setVariableValueOptions} options object
* @example
* await API.RAPID.setVariableValue("MainModule","aa", 2,"T_ROB1",{syncPers:true});
*/
this.setVariableValue = async function (module, name, value, task = 'T_ROB1', {syncPers = false} = {}) {
try {
let variable = await API.RAPID.createVariableInstance(module, name, task);
// For variable whose data type is not string, the value is considered as raw value when the value type is string;
if (
typeof value == 'string' &&
(variable.props.dataType != 'string' ||
(variable.props.dataType == 'string' && variable.props.dimensions.length > 0))
) {
await variable.setRawValue(value);
} else {
await variable.setValue(value);
}
if (syncPers) {
await API.RWS.requestMastership();
await API.RWS.RAPID.syncPers(module, task);
}
} finally {
if (syncPers) {
const bHeldEditMasterShip = await API.RWS.checkIfHeldMastership();
if (bHeldEditMasterShip) {
await API.RWS.releaseMastership();
}
}
}
};
this.unsubscribeVariable = async function (task, module, name) {
const refName = task + ':' + module + ':' + name;
let entry = this.subscriptions.get(refName);
if (entry && --entry.count === 0) {
await entry.variable.unsubscribe();
this.subscriptions.delete(refName);
}
};
/**
* Subscribe to a existing RAPID variable.
* @alias getVariableInstance
* @memberof API.RAPID
* @param {string} task - RAPID Task in which the variable is contained
* @param {string} module -RAPID module where the variable is contained
* @param {string} name - name of RAPID variable
* @param {boolean} subscribe - subscribe to variable, default is true
* @returns RWS.RAPID Data object
* @private
*/
const getVariableInstance = async (task, module, name, subscribe = true) => {
if (task && module && name) {
try {
const refName = task + ':' + module + ':' + name;
let entry = this.subscriptions.get(refName);
if (entry) {
entry.count++;
return entry.variable;
} else {
let newVariable = await this.createVariableInstance(module, name, task);
// double check case parallel async subscriptions just happened
let entry = this.subscriptions.get(refName);
if (!entry) {
if (API.isSubscriptionBlocked) {
Logger.w(WarningType.RWSSubscriptionBlocked, `API.RAPID: Subscription disabled, variable: ${refName}`);
} else {
if (subscribe) {
this.subscriptions.set(refName, {variable: newVariable, count: 1});
if (newVariable.symbolType !== 'constant') newVariable.subscribe();
}
}
return newVariable;
} else {
return entry.variable;
}
}
} catch (e) {
return API.rejectWithStatus(`Failed to subscribe to variable ${name} at ${task}->${module} module.`, e, {
errorCode: ErrorCode.FailedToSubscribeResource,
});
}
}
};
this.getVariable = getVariableInstance;
this.getVariableInstance = getVariableInstance;
/**
* Gets an instance of a API.RAPID.Task class
* @alias getTask
* @memberof API.RAPID
* @param {string} task - Task name
* @returns {Promise<object>} - API.RAPID.Task
* @example
* await API.RAPID.getTask();
*/
this.getTask = async function (task = 'T_ROB1') {
const t = await RWS.Rapid.getTask(task);
return new Task(t);
};
/**
* Load a module from the controller HOME files
* @alias loadModule
* @memberof API.RAPID
* @param {string} path Path to the module file in the HOME directory (included extension of the module).
* @param {boolean} replace f true, it will replace an existing module in RAPID with the same name
* @param {string} taskName The name of the task to which the module belongs.
* @param {boolean} [requestMsh] Edit mastership needs to be requested before loading the module.
* @note SPOC systems (e.g., RobotWare 8): Write access must be held before loading module. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
* @note Non-SPOC systems (e.g., RobotWare 7): Edit mastership is handled internally by this interface.
* @example
* let url = `${this.path}/${this.name}${this.extension}`;
* await API.RAPID.loadModule(url, true);
*/
const loadModule = async function (path, replace = false, taskName = 'T_ROB1', requestMsh = true) {
await API.RWS.RAPID.loadModule.apply(null, arguments);
};
this.loadModule = loadModule;
/**
* Gets the RAPID module text.
* @param {string} moduleName The module name.
* @param {string} taskName The task name.
* @returns {Promise<string>} Returns the module text.
* @example
* await API.RAPID.getModuleText("MainModule");
*/
this.getModuleText = async function (moduleName, taskName = 'T_ROB1') {
const res = await API.RWS.RAPID.getModuleText(moduleName, taskName);
const moduleText = res['module-text'];
if (moduleText) return moduleText;
const filePath = res['file-path'];
const fileContent = await API.FILESYSTEM.getFile(filePath);
await API.FILESYSTEM.deleteFile(filePath);
return fileContent;
};
/**
* Set RAPID module text
* @note SPOC systems (e.g., RobotWare 8): Write access must be held before modifying module text. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
* @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as edit mastership is handled internally.
* @param {string} moduleName The module name
* @param {string} text The text to write
* @param {string} taskName The task name
* @example
* await API.RAPID.setModuleText("Module1",`MODULE Module1\r\nPROC proc1()\r\nTPWrite "123";\r\nENDPROC\r\nENDMODULE`);
*/
this.setModuleText = async function (moduleName, text, taskName = 'T_ROB1') {
try {
await API.RWS.requestMastership('edit');
try {
await API.RWS.RAPID.setModuleText(moduleName, text, taskName);
} catch (e) {
return API.rejectWithStatus(`Failed to set module ${taskName}:${moduleName} text.`, e, {
errorCode: ErrorCode.FailedToSetRapidModuleContent,
});
}
} finally {
await API.RWS.releaseMastership('edit');
}
};
/**
* Unloads a RAPID module
* @alias unloadModule
* @memberof API.RAPID
* @param {string} moduleName The module name.
* @param {string} [taskName] The task name.
* @example
* await API.RAPID.unloadModule("Module1")
*/
const unloadModule = async function (moduleName, taskName = 'T_ROB1') {
// await API.RWS.requestMastership('edit');
await API.RWS.RAPID.unloadModule.apply(null, arguments);
// await API.RWS.releaseMastership('edit');
};
this.unloadModule = unloadModule;
/**
* Prepare actions before executing program / procedure
*/
const prepareExecution = async function (setPP = true, moduleName = '', procName = '', taskName = 'T_ROB1') {
// check write access status if SPOC system
const isSpocSystem = await API.RWS.CONTROLSTATION.isSpocSystem();
if (isSpocSystem) {
await API.RWS.CONTROLSTATION.checkIfHeldWriteAccess();
}
let exeState = await RWS.Rapid.getExecutionState();
if (exeState === RWS.Rapid.ExecutionStates.running) {
throw new Error(ErrorCode.ProgramAlreadyExecuting);
}
let ctrlState = await RWS.Controller.getControllerState();
if (ctrlState !== API.CONTROLLER.STATE.MotorsOn) {
throw new Error(ErrorCode.FailedToExecuteWithMotorOff);
}
let bExecuteProc = moduleName && procName;
if (bExecuteProc) {
await API.RAPID.setPPToRoutine(moduleName, procName, taskName);
} else {
let pcpRes = await API.RAPID.getProgramPointer();
if (!pcpRes['moduleName']) {
if (setPP) {
await API.RAPID.setPPToMain();
} else {
throw new Error(ErrorCode.FailedToExecuteWithoutPointer);
}
}
}
};
/**
* Sets cycle mode
* @note SPOC systems (e.g., RobotWare 8): Write access must be held before setting cycle mode. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
* @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as edit mastership is handled internally.
* @alias setCycleMode
* @memberof API.RAPID
* @param {string} cycleMode The value can be 'forever' or 'once'.
* @example
* await API.RAPID.setCycleMode("once")
*/
this.setCycleMode = async function (cycleMode = 'once') {
try {
await RWS.Mastership.request();
try {
await API.RWS.RAPID.setCycleMode(cycleMode);
} catch (e) {
return API.rejectWithStatus(`Failed to set cycle mode to ${cycleMode}.`, e, {
errorCode: ErrorCode.FailedToSetCycleMode,
});
}
} finally {
await RWS.Mastership.release();
}
};
/**
* Restore cycle mode after program execution is stopped
*/
const restoreCycleAfterProgramExecution = async function (preCycleMode, taskName = 'T_ROB1') {
if (r.isSubscriptionBlocked) {
Logger.w(WarningType.RWSSubscriptionBlocked, 'API.RAPID: Subscription disabled, monitor execution status');
return;
}
const monitor = RWS.Rapid.getMonitor(RWS.Rapid.MonitorResources.execution, taskName);
const executionCallback = async function (data) {
Logger.i(logModule, `program execution status: ${data}`);
if (data == RWS.Rapid.ExecutionStates.stopped) {
try {
// restore cycle mode
await API.RAPID.setCycleMode(preCycleMode);
// unsubscribe resource after program stopped
await monitor.unsubscribe();
} catch (e) {
return API.rejectWithStatus(`Failed to recover cycle mode ${preCycleMode} after program execution`, e);
}
}
};
// subscribe program execution status
monitor.addCallbackOnChanged(executionCallback.bind(this));
await monitor.subscribe();
};
/**
* Starts program execution
* @note SPOC systems (e.g., RobotWare 8): Write access must be held before starting RAPID program execution. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
* @note Non-SPOC systems (e.g., RobotWare 7): Use this interface directly as edit mastership is handled internally.
* @alias startExecution
* @memberof API.RAPID
* @param {string} cycleMode Cycle mode: forever|as_is|once
* @param {string} execMode Execution mode: continue|stepin|stepover|stepout|stepback|steplast|stepmotion
* @param {string} regainMode Regain mode: continue|regain|clear
* @param {boolean} setPP Set program pointer to main if has no pp
* @param {string} moduleName Module name
* @param {string} procName Procedure name
* @param {string} taskName Task name
* @example
* await API.RAPID.startExecution({moduleName: "Wizard", procName: "test1"});
*/
this.startExecution = async function ({
cycleMode = 'once',
execMode = 'continue',
regainMode = 'continue',
setPP = true,
moduleName = '',
procName = '',
taskName = 'T_ROB1',
} = {}) {
let isas = cycleMode == 'as_is';
let preCycleMode = 'once';
if (!isas) {
// get current cycle mode
preCycleMode = await API.RWS.RAPID.getCycleMode();
isas = preCycleMode == cycleMode;
}
let cycle = cycleMode;
await prepareExecution(setPP, moduleName, procName, taskName);
try {
await RWS.Rapid.startExecution({regainMode: regainMode, executionMode: execMode, cycleMode: cycle});
if (!isas) {
await restoreCycleAfterProgramExecution(preCycleMode, taskName);
}
} catch (error) {
// if (error.code) {
// const errorCode = error.code;
// if (errorCode == '-1073445867') {
// // <span class="description">The operation was not allowed for the given user.</span>
// throw new Error(ErrorCode.FailedToExecuteWithoutPermissions);
// } else if (errorCode == '-1073442807') {
// // <span class="description">The robot is not on path and is unable to restart. Regain to or clear path.</span>
// throw new Error(ErrorCode.FailedToExecuteOutsideRegain);
// }
// }
let executionState = await RWS.Rapid.getExecutionState();
if (executionState == RWS.Rapid.ExecutionStates.running) {
Logger.i(InfoType.RobotOperation, 'Regain is performing');
} else {
throw new Error(ErrorCode.FailedToStartProgramExecution, {cause: error});
}
}
};
/**
* @typedef stopExecutionProps
* @prop {RWS.Rapid.StopModes} [stopMode] Stop mode, valid values: 'cycle', 'instruction', 'stop' or 'quick_stop'
* @prop {RWS.Rapid.UseTSPOptions.normal} [useTSP] Use task selection panel, valid values: 'normal' or 'all_tasks'
* @memberof API.RAPID
*/
/**
* Stops the RAPID execution with the settings given in the parameter object. All or any of the defined parameters can be supplied, if a value is omitted a default value will be used. The default values are:
* stopMode = 'stop', and
* useTSP = 'normal'
* @alias stopExecution
* @memberof API.RAPID
* @param {stopExecutionProps} props
* @example
* await API.RAPID.stopExecution();
*/
this.stopExecution = async function ({
stopMode = RWS.Rapid.StopModes.stop,
useTSP = RWS.Rapid.UseTSPOptions.normal,
} = {}) {
var state = await RWS.Rapid.getExecutionState();
if (state === RWS.Rapid.ExecutionStates.running) {
try {
await RWS.Rapid.stopExecution({
stopMode,
useTSP,
});
} catch (e) {
return API.rejectWithStatus('Failed to stop program execution', e, {
errorCode: ErrorCode.FailedToStopProgramExecution,
});
}
}
};
/**
* Searchs all routines
* @alias searchRoutines
* @memberof API.RAPID
* @param {boolean} allread If the value is set to true, the non-service routines will also be included.
* @param {string} taskName The task name
* @returns {Promise<API.RAPID.routines[]>} A list of API.RAPID.routines
* @example
* await task.searchRoutines();
*/
this.searchRoutines = async function (allread = true, taskName = 'T_ROB1') {
// check execution status
let exeState = await RWS.Rapid.getExecutionState();
if (exeState === RWS.Rapid.ExecutionStates.running) {
throw new Error(ErrorCode.FailedToOperateDuringExecution, {
cause: 'Cannot update routines during program execution',
});
}
let searchedResult = null;
let searchFlag = false;
let exturl = 'tasks/' + taskName + '/serviceroutine?';
let result = [];
try {
while (!searchFlag) {
let routineRes = await API.RWS.RAPID.getServiceRoutines(exturl, allread);
let routines = routineRes['state'];
for (const key in routines) {
let url2RoutineArr = routines[key]['url-to-routine'].split('/');
result.push({
routineName: routines[key]['routine_name'],
moduleName: url2RoutineArr[2],
taskName: url2RoutineArr[1],
bServiceRoutine: routines[key]['service-routine'] == 'TRUE',
});
}
// Check if there's next page
if (typeof routineRes._links.next == 'undefined') {
searchFlag = true;
} else {
exturl = routineRes._links.next.href + (allread ? '&' : '');
}
}
return result;
} catch (error) {
return API.rejectWithStatus(`Failed to search routine list in task ${taskName}`, error, {
errorCode: ErrorCode.FailedToSearchRoutines,
});
}
};
})();
r.constructedRapid = true;
};
if (typeof API.constructedRapid === 'undefined') {
factoryApiRapid(API);
}
export default API;