import API from './ecosystem-base.js';
import {Logger} from './../function/log-helper.js';
import {ErrorCode} from '../exception/exceptionDesc.js';
import {InfoType, WarningType} from '../information/informationCode.js';
const factoryApiCfg = function (cfg) {
const logModule = 'ecosystem-cfg';
/**
* The API.CONFIG namespace provides a set of interfaces for managing controller configurations.
* It includes methods for loading configuration files, retrieving and updating attributes of signals and cross-connections, and creating or deleting configuration instances.
* This namespace serves as a high-level abstraction for interacting with the controller's configuration database, enabling developers to efficiently manage and customize controller configurations.
* @alias API.CONFIG
* @namespace
* @property {enum} DOMAIN Configuration domain, e.g. EIO
* @property {enum} TYPE
*/
cfg.CONFIG = new (function () {
/**
* Enum for configuration domains
* @readonly
* @enum {string}
* @memberof API.CONFIG
* @property {string} EIO - I/O system domin.
* @property {string} SYS - Controller domin.
* @property {string} MOC - Motion control domain.
* @property {string} MMC - Man-machine communication domain.
* @property {string} SIO - Communication domain.
* @property {string} PROC - PROC domain.
* @example
* let config = await RWS.CFG.getInstanceByName(API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.SIGNAL, "Auto");
*/
this.DOMAIN = {
EIO: 'EIO',
SYS: 'SYS',
MOC: 'MOC',
MMC: 'MMC',
SIO: 'SIO',
PROC: 'PROC',
};
/**
* Enum for configuration types
* @readonly
* @enum {string}
* @memberof API.CONFIG
* @property {string} SIGNAL - Signal configuration.
* @property {string} CROSS - Cross-connection configuration.
* @property {string} ETHERNETIP - Ethernet/IP device configuration.
*/
this.TYPE = {
SIGNAL: 'EIO_SIGNAL',
CROSS: 'EIO_CROSS',
ETHERNETIP: 'ETHERNETIP_DEVICE',
};
/**
* Verifies a configuration file.
* @alias verifyConfiguration
* @memberof API.CONFIG
* @param {string} filepath The file path, including the file name.
* @param {RWS.CFG.LoadMode} action="replace" the validation method, The validation method, whose valid values include "add", "replace" and "add-with-reset".
* @example
* // Validates a EIO cfg file in home directory into controller
* await API.CONFIG.verifyConfiguration("$HOME/eio.cfg")
*/
this.verifyConfiguration = async function (filepath, action = RWS.CFG.LoadMode.add) {
try {
return await RWS.CFG.verifyConfigurationFile(filepath, action);
} catch (e) {
return API.rejectWithStatus(`Failed to verify controller configuration file in path ${filepath} `, e, {
errorCode: ErrorCode.FailedToVerifyConfiguration,
});
}
};
/**
* Loads a configuration file to the controller configuration data base.
* A controller restart is required for the new configuration to take effect.
* @alias loadConfiguration
* @memberof API.CONFIG
* @param {string} filepath The file path, including the file name.
* @param {RWS.CFG.LoadMode} action="replace" The validation method, whose valid values include "add", "replace" and "add-with-reset".
* @example
* // Load a EIO cfg file in home directory into controller
* await API.CONFIG.loadConfiguration("$HOME/eio.cfg")
*/
this.loadConfiguration = async function (filepath, action = RWS.CFG.LoadMode.add) {
try {
return await RWS.CFG.loadConfiguration(filepath, action);
} catch (e) {
return API.rejectWithStatus(`Failed to load controller configuration file in path ${filepath} `, e, {
errorCode: ErrorCode.FailedToLoadConfiguration,
});
}
};
/**
* Gets the attributes of the signal
* @alias getSignalAttributes
* @memberof API.CONFIG
* @param {string} name The name of the signal
* @return {Promise<object>} The attributes of a signal
* @example
* // get the signal attribute of the signal Auto
* let attributes = await API.CONFIG.getSignalAttributes("Auto")
*/
this.getSignalAttributes = async function (name) {
try {
// Check if instance is configured (which does not necesarily means that the signal is already availabe),
// after creating an instance, a Reboot is reqiured
let config = await RWS.CFG.getInstanceByName(API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.SIGNAL, name);
let attr = null;
if (config !== null) attr = config.getAttributes();
return attr;
} catch (e) {
// if singal not available, then an exception will occur
return API.rejectWithStatus(`Error while getting attributes of a signal ${name}`, e, {
errorCode: ErrorCode.FailedToGetSignalAttributes,
});
}
};
/**
* Gets the attributes of a Cross-Conneciton configuraiton instance
* @alias getCrossConnectionAttributes
* @memberof API.CONFIG
* @param {string} name The name of the Cross-Connection configuration instance.
* @return {Promise<object>} The attributes of a Cross-Connection configuration instance
* @example
* let attributes = await API.CONFIG.getCrossConnectionAttributes("MotorsOnInAuto")
*/
this.getCrossConnectionAttributes = async function (name) {
try {
// Check if instance is configured (which does not necesarily means that the signal is already availabe),
// after creating an instance, a Reboot is reqiured
let config = await RWS.CFG.getInstanceByName(API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.CROSS, name);
let attr = null;
if (config !== null) attr = config.getAttributes();
return attr;
} catch (e) {
// if singal not available, then an exception will occur
return API.rejectWithStatus(`Error while getting attributes of the cross-conneciton ${name} `, e, {
errorCode: ErrorCode.FailedToGetCrossConnectionAttributes,
});
}
};
/**
* Creates a signal configuraiton instance with attributes
* @alias createSignalInstance
* @note SPOC systems (e.g., RobotWare 8): Write access must be held before creating a signal instance. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
* @note Non-SPOC systems (e.g., RobotWare 7): Edit mastership must be held before creating a signal instance. Use {@link API.RWS.requestMastership} to acquire write access.
* @memberof API.CONFIG
* @param {object} attr The signal attributes.
* @returns {Promise<any>} - A Promise object whose value is empty when it is resolved or contains a status when it is rejected
* @example
* // create a digital output signal named "TestDO1" with read-only acess level
* await API.CONFIG.createSignalInstance({Name:"TestDO1",Access:"ReadOnly",SignalType:"DO"})
*/
this.createSignalInstance = async function (attr) {
try {
await RWS.CFG.createInstance(API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.SIGNAL, attr.Name);
await this.updateSignalAttributes(attr);
} catch (e) {
return API.rejectWithStatus(`Create signal instance ${attr.Name} failed`, e, {
errorCode: ErrorCode.FailedToCreateSignal,
});
}
};
/**
* Creates a cross conneciton configuration instance with specified attributes
* @alias createCrossConnectionInstance
* @memberof API.CONFIG
* @note SPOC systems (e.g., RobotWare 8): Write access must be held before creating a cross connection instance. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
* @note Non-SPOC systems (e.g., RobotWare 7): Edit mastership must be held before creating a cross connection instance. Use {@link API.RWS.requestMastership} to acquire write access.
* @param {object} attr The attributes including cross connection name and others.
* @returns {Promise<any>} - A Promise object whose value is empty when it is resolved or contains a status when it is rejected
* @example
* await API.CONFIG.createCrossConnectionInstance({Name:"TestCrossConnection",Act1:"GOFA_SysOut_SetMotorsOn",Act1_invert:"false",Res:"GOFA_SysIn_SetMotorsOn"});
*/
this.createCrossConnectionInstance = async function (attr) {
try {
await RWS.CFG.createInstance(API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.CROSS, attr.Name);
this.updateCrossConnectionAttributes(attr);
} catch (e) {
return API.rejectWithStatus(`Create cross connection instance ${attr.Name} failed`, e, {
errorCode: ErrorCode.FailedToCreateCrossConnection,
});
}
};
/**
* Updates attributes of a signal instance in the configuration database.
* The attributes object should only contain valid members, i.e. attributes that are valid for the instance’s specific type, and the corresponding values.
* @alias updateSignalAttributes
* @memberof API.CONFIG
* @note SPOC systems (e.g., RobotWare 8): Write access must be held before updating signal attribute. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
* @note Non-SPOC systems (e.g., RobotWare 7): Edit mastership must be held before updating signal attribute. Use {@link API.RWS.requestMastership} to acquire write access.
* @param {object} attr The attributes to be updated
* @returns {Promise<object>} - A Promise with an instance object
* @example
* // update the access-level of signal "TestDO1" to all
* await API.CONFIG.updateSignalAttributes({Name:"TestDO1",Access:"All"})
*/
this.updateSignalAttributes = async function (attr) {
if (attr.Device === '') {
Logger.i(InfoType.RobotOperation, 'Trying to update signal attributes with empty device');
delete attr.Device;
await this.deleteSignal(attr.Name);
await RWS.CFG.createInstance(API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.SIGNAL, attr.Name);
}
await RWS.CFG.updateAttributesByName(API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.SIGNAL, attr.Name, attr);
};
/**
* Updates attributes of a cross connection instance in the configuration data-base.
* The attributes object should only contain valid members, i.e. attributes that are valid for the instance’s specific type and the corresponding values.
* @note SPOC systems (e.g., RobotWare 8): Write access must be held before updating cross connection attribute. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
* @note Non-SPOC systems (e.g., RobotWare 7): Edit mastership must be held before updating cross connection attribute. Use {@link API.RWS.requestMastership} to acquire write access.
* @alias updateCrossConnectionAttributes
* @memberof API.CONFIG
* @param {object} attr The attributes to be updated
* @returns {Promise<object>} - A Promise with an instance object
* await API.CONFIG.updateCrossConnectionAttributes({Name:"TestCrossConnection",Act1:"GOFA_SysOut_SetMotorsOn",Act1_invert:"true",Res:"GOFA_SysIn_SetMotorsOn"});
*/
this.updateCrossConnectionAttributes = async function (attr) {
await RWS.CFG.updateAttributesByName(API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.CROSS, attr.Name, attr);
};
/**
* Gets all the instances of a specified type
* @alias fetchAllInstancesOfType
* @param {API.CONFIG.TYPE} type - All instances of the type will be returned.
* @memberof API.CONFIG
* @returns {Promise<object[]>} - A Promise with a list of objects
* @example
* let instances = await API.CONFIG.fetchAllInstancesOfType(API.CONFIG.TYPE.CROSS)
*/
this.fetchAllInstancesOfType = async function (type) {
try {
const instances = await RWS.CFG.getInstances(API.CONFIG.DOMAIN.EIO, type);
return instances;
} catch (e) {
return API.rejectWithStatus(`Failed to fetch instances of type ${type}`, e, {
errorCode: ErrorCode.FailedToFetchCfgInstance,
});
}
};
/**
* Gets all cross-connection instances
* @alias fetchAllCrossConnections
* @memberof API.CONFIG
* @returns {Promise<object[]>} - A Promise with a list of all cross connection objects
* @example
* let connections = await API.CONFIG.fetchAllCrossConnections()
*/
this.fetchAllCrossConnections = async function () {
return await this.fetchAllInstancesOfType(API.CONFIG.TYPE.CROSS);
};
/**
* Gets all the signal instances
* @alias fetchAllSignals
* @memberof API.CONFIG
* @returns {Promise<object[]>} - A Promise with a list of objects
* @example
* let signals = await API.CONFIG.fetchAllSignals()
*/
this.fetchAllSignals = async function () {
return await this.fetchAllInstancesOfType(API.CONFIG.TYPE.SIGNAL);
};
/**
* Delete a cross-conneciton instance from the configuration data-base.
* @alias deleteCrossConnection
* @note SPOC systems (e.g., RobotWare 8): Write access must be held before deleting cross connection. 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.
* @memberof API.CONFIG
* @returns {Promise<any>} - A Promise which is empty if it resolves and contains a status if it is rejected.
* @private
*/
this.deleteCrossConnection = async function (name) {
try {
return await API.RWS.CFG.deleteConfigInstance(name, API.CONFIG.TYPE.CROSS, API.CONFIG.DOMAIN.EIO);
} catch (e) {
return API.rejectWithStatus(`Failed to delete cross connection ${name}`, e, {
errorCode: ErrorCode.deleteCrossConnection,
});
}
};
/**
* Delete a signal instance from the configuration data-base.
* @alias deleteSignal
* @note SPOC systems (e.g., RobotWare 8): Write access must be held before deleting signal. 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.
* @memberof API.CONFIG
* @returns {Promise<any>} - A Promise which is empty if it resolves and contains a status if it is rejected.
* @private
*/
this.deleteSignal = async function (name) {
try {
return await API.RWS.CFG.deleteConfigInstance(name, API.CONFIG.TYPE.SIGNAL, API.CONFIG.DOMAIN.EIO);
} catch (e) {
return API.rejectWithStatus(`Failed to delete signal ${name}`, e, {errorCode: ErrorCode.FailedToDeleteSignal});
}
};
})();
/**
* The API.DEVICE class provides a set of interfaces for managing and interacting with devices connected to the controller.
* It includes methods for searching available devices, retrieving device attributes, and mapping signals to devices.
* This class simplifies the process of managing device configurations and ensures seamless integration with the controller's ecosystem.
* @alias API.DEVICE
* @namespace
*/
cfg.DEVICE = new (function () {
/**
* @alias fetchEthernetIPDevices
* @memberof API
* @returns {Promise<object[]>} - List of device objects including containing signals
* @private
*/
const fetchEthernetIPDevices = async function () {
const ethernetIPDevices = [];
try {
const ethIPDevices = await RWS.CFG.getInstances(API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.ETHERNETIP);
for (let device of ethIPDevices) {
ethernetIPDevices.push({
name: device.getInstanceName(),
signals: [],
});
}
const eioSignals = await RWS.CFG.getInstances(API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.SIGNAL);
for (const signal of eioSignals) {
let attr = signal.getAttributes();
for (let item of ethernetIPDevices) {
attr.Device === item.name && item.signals.push({attributes: attr});
}
}
return ethernetIPDevices;
} catch (e) {
return API.rejectWithStatus(`Failed to fetch the EthernetIP devices`, e, {
errorCode: ErrorCode.FailedToFetchEthernetIPDevices,
});
}
};
/**
* Search for the available Ethernet/IP devices
* @alias searchEthernetIPDevices
* @memberof API.DEVICE
* @returns {Promise<string[]>} List of device names
* @example
* let devices = await API.DEVICE.searchEthernetIPDevices()
*/
this.searchEthernetIPDevices = async function () {
const devices = [];
const ethIPDevices = await fetchEthernetIPDevices();
for (let device of ethIPDevices) {
devices.push(device.name);
}
return devices;
};
/**
* @typedef API.DEVICE.SignalAttributesProp
* @prop {string} [name] Name of the signal
* @prop {string} [device] Device to which the signal is assigned
* @prop {string} [map] Mapping number
* @prop {string} [type] Type of signal: 'DI' | 'DO' | 'AI' | 'AO' | 'GI' | 'GO'
*/
/**
* Finds the signal that owns the attributes
* @alias find
* @memberof API.DEVICE
* @param {API.DEVICE.SignalAttributesProp} attr An object with the following information:
* <br> (string) name: signal name
* <br> (string) device: device name
* <br> (string) map: device map name
* <br> (string) type: signal type: : 'DI' | 'DO' | 'AI' | 'AO' | 'GI' | 'GO'
* @returns {Promise<object | undefined>} API.Signal instance if found; otherwise undefined.
*/
this.find = async function (attr) {
try {
const ethIPDevices = await fetchEthernetIPDevices();
const checkCondition = function (device) {
return (
device &&
device.signals.find(
(signal) =>
(Object.prototype.hasOwnProperty.call(attr, 'map') ? signal.attributes.DeviceMap === attr.map : true) &&
(Object.prototype.hasOwnProperty.call(attr, 'type')
? signal.attributes.SignalType === attr.type
: true) &&
(Object.prototype.hasOwnProperty.call(attr, 'name') ? signal.attributes.Name === attr.name : true),
)
);
};
let found = undefined;
if (Object.prototype.hasOwnProperty.call(attr, 'device')) {
const device = ethIPDevices.find((dev) => dev.name === attr.device);
found = checkCondition(device);
} else {
for (const device of ethIPDevices) {
found = checkCondition(device);
if (found) break;
}
}
return found ? API.SIGNAL.getSignal(found.attributes.Name) : found;
} catch (e) {
return API.rejectWithStatus('Failed to get signal list with specific attribute', e, {
errorCode: ErrorCode.FailedToGetSiganlsWithAttribute,
});
}
};
})();
/**
* This class provides a set of interfaces for managing and interacting with signals on the controller.
* It includes methods for creating, retrieving, and updating signal configurations, subscribing to signal changes, and searching for signals based on various filters.
* This class simplifies signal management and enables seamless integration with the controller's configuration and runtime environment.
* @alias API.SIGNAL
* @namespace
* @property {API.SIGNAL.Signal} Signal
*/
cfg.SIGNAL = new (function () {
this.signals = [];
/**
* The Singal class contains all the relevant elements to access the signal and its configurations.
* Instance of this class can be created by {@link API.SIGNAL.createSignal()} when creating a new signal
* or {@link API.SIGNAL.getSignal()} when looking for an existing one.
* @class Signal
* @memberof API.SIGNAL
* @param {string} name The signal name.
* @param {string|object} signal Instance of a RWS.IO.getSignal(name)
* @param {object} config Object from RWS.CFG.getInstanceByName(
* API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.SIGNAL, name);
* @param {Object} attr The signal attributes. Please refer the response of config.getAttributes().
* @example
* const mySignal = await API.SIGNAL.getSignal('signal_name');
* @todo define the config object returne from RWS
*/
class Signal {
constructor(name, signal, attr) {
this.name = name;
this.signal = signal;
this._attr = attr;
this.InstanceType = API.CONFIG.TYPE.SIGNAL;
this.signal ? (this.modified = false) : (this.modified = true);
}
async getValue() {
try {
await this.signal.fetch();
return await this.signal.getValue();
} catch (e) {
return API.rejectWithStatus(`Failed to get value of signal ${this.name}`, e, {
errorCode: ErrorCode.FailedToGetSignalValue,
}); // fallback value
}
}
async setValue(value) {
try {
// check if signal value is accptable
if (this.type === 'DI' || this.type === 'DO') {
if (typeof value !== 'number' || (typeof value == 'number' && ![1, 0].includes(value))) {
return API.rejectWithStatus(
`Signal ${this.name} is a digital signal and can only be set to 0/1.`,
{},
{errorCode: ErrorCode.InvalidSignalValue},
);
}
}
if (this.type === 'AI' || this.type === 'AO' || this.type === 'GI' || this.type === 'GO') {
if (typeof value !== 'number') {
// AI and AO can only be set to number values
return API.rejectWithStatus(
`Signal ${this.name} is an analog / group signal and can only be set to number values.`,
{},
{errorCode: ErrorCode.InvalidSignalValue},
);
}
}
if (!this.signal)
return API.rejectWithStatus(
`Signal ${this.name} not yet availabe. If confiugred a system restart may be required.`,
{},
{errorCode: ErrorCode.InvalidSignalValue},
);
return await this.signal.setValue(value);
} catch (e) {
return API.rejectWithStatus(`Failed to set value of signal ${this.name}`, e, {
errorCode: ErrorCode.FailedToSetSignalValue,
});
}
}
get type() {
return this._attr.SignalType;
}
get device() {
return this._attr.Device;
}
get map() {
return this._attr.DeviceMap;
}
get active() {
return this.signal === null ? false : true;
}
// set attr(a) {
// const f = function (key) {
// this._attr[key] = a[key];
// };
// Object.keys(a).forEach(f.bind(this));
// API.SIGNAL.updateSignalAttributes(a);
// this.modified = true;
// }
async updateSignalAttributes(a) {
const f = function (key) {
this._attr[key] = a[key];
};
Object.keys(a).forEach(f.bind(this));
await API.CONFIG.updateSignalAttributes(a);
this.modified = true;
}
/**
* Subscribe to the signal and set the callback function
* @param {boolean} [raiseInitial] flag indicating whether an initial event is raised when subscription is registered
* @returns {undefined | Promise<any>} - undefine if success, otherwise a reject Promise
*/
async subscribe(raiseInitial = false) {
try {
if (!this.signal) throw new Error(`Signal ${this.name} not available for subscription`);
if (API.isSubscriptionBlocked) {
Logger.w(WarningType.RWSSubscriptionBlocked, `API.SIGNAL: Subscription disabled, signal: ${this.name}`);
return;
} else {
return await this.signal.subscribe(raiseInitial);
}
} catch (e) {
return API.rejectWithStatus(`Failed to subscribe to signal ${this.name}`, e, {
errorCode: ErrorCode.FailedToSubscribeSignal,
});
}
}
async unsubscribe() {
return await this.signal.unsubscribe();
}
onChanged(callback) {
try {
if (!this.signal) {
throw new Error(`Signal ${this.name} not available for subscription`);
}
const cb = async (value) => {
// first time this is called, newValue is undefined.
if (value === undefined) {
value = await this.signal.getValue();
}
callback(value);
};
this.signal.addCallbackOnChanged(cb.bind(this));
} catch (e) {
return API.rejectWithStatus(`Failed to add callback to signal ${this.name}`, e, {
errorCode: ErrorCode.FailedToAddSignalCallback,
});
}
}
}
/**
* Creates a new signal in the configuration database if there is no one existed.
* If there is an existing one, the attributes of the new signal will inherit from those of the existing one.
* @alias createSignal
* @note SPOC systems (e.g., RobotWare 8): Write access must be held before creating a signal. Use {@link API.RWS.CONTROLSTATION.requestWriteAccess} to acquire write access explicitly.
* @note Non-SPOC systems (e.g., RobotWare 7): Edit mastership must be held before creating a signal. Use {@link API.RWS.requestMastership} to acquire write access.
* @memberof API.SIGNAL
* @param {string} name Signal name.
* @param {*} attr Signal attributes.
* @returns {Promise<API.SIGNAL.Signal>}
* @example
* await API.SIGNAL.createSignal("TestDO2",{SignalType:"DO", Access:'All'})
*/
this.createSignal = async (name, attr = {}) => {
const found = this.signals.find((s) => s.name === name);
if (found) return found;
let signal = null;
attr.Name = name;
try {
signal = await RWS.IO.getSignal(name);
let config = await RWS.CFG.getInstanceByName(API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.SIGNAL, name);
attr = await config.getAttributes();
} catch (e) {
try {
let config = await RWS.CFG.getInstanceByName(API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.SIGNAL, name);
attr = await config.getAttributes();
} catch (e) {
// Signal instance NOT found, create a config instance
if (!attr.Name || !attr.SignalType || !attr.Access)
return API.rejectWithStatus(
`Mandatory attributes missing to find the signal instance: ${!attr.Name ? 'Name ' : ''}${!attr.SignalType ? 'SignalType ' : ''}${
!attr.Access ? 'Access ' : ''
} `,
{},
{errorCode: ErrorCode.MissingMandatoryAttributes},
);
API.CONFIG.createSignalInstance(attr);
let config = await RWS.CFG.getInstanceByName(API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.SIGNAL, attr.Name);
}
}
const s = new Signal(name, signal, attr);
if (signal) {
s.subscribe();
this.signals.push(s);
}
return s;
};
/**
* Gets an instance of API.CONFIG.Signal connected to an existing signal .
* If no existing signal is found, a Promise.reject is returned
* @alias getSignal
* @memberof API.SIGNAL
* @param {string} name The signal name.
* @param {string} network The network name.
* @param {string} device The device name.
* @returns {Promise<object>}
* await API.SIGNAL.getSignal("TestDO1")
*/
this.getSignal = async function (name, network = '', device = '') {
if (name) {
let found = this.signals.find((signal) => signal.name === name);
if (found) return found;
try {
const signal = await API.RWS.SIGNAL.getSignalInstance(name, network, device);
let config = await RWS.CFG.getInstanceByName(API.CONFIG.DOMAIN.EIO, API.CONFIG.TYPE.SIGNAL, name);
let attr = config.getAttributes();
const s = new Signal(name, signal, attr);
//check again before pushing
const findIndex = this.signals.findIndex((signal) => signal.name === name);
findIndex !== -1 ? (this.signals[findIndex] = s) : this.signals.push(s);
return s;
} catch (e) {
return API.rejectWithStatus(`Failed to get signal ${name}`, e, {errorCode: ErrorCode.FailedToGetSignal});
}
} else {
return API.rejectWithStatus(
'API.SIGNAL.getSignal: name parameter is empty!',
{},
{errorCode: ErrorCode.EmptySignalName},
);
}
};
/**
* @typedef filterSignalSearch
* @prop {string} [name]
* @prop {string} [device]
* @prop {string} [network]
* @prop {string} [category]
* @prop {'DI' | 'DO' | 'AI' | 'AO' | 'GI' | 'GO'} [type]
* @prop {string} [invert]
* @prop {string} [blocked]
* @memberof API.CONFIG.SIGNAL
*/
/**
* Searches for available signals in the controller.
* @alias search
* @memberof API.SIGNAL
* @param {API.SIGNAL.filterSignalSearch} filter - The result can be filtered by using
* an object with the following possible elements:
* <br> (string) name signal name
* <br> (string) device device name
* <br> (string) network network name
* <br> (string) category category string
* <br> (string) category-pon
* <br> (string) type type of signal, valid values: 'DI', 'DO', 'AI', 'AO', 'GI' or 'GO'
* <br> (boolean) blocked blocked signals
*
* Searches the system for signals that meet the filter conditions.
* The signal name can be a fuzzy matching, which means that the name of any signal containing a filter value will be returned.
* The filter type can contain multiple types by using the values within '[', ']' and separated by ',', e.g. '["DI","DO"]'.
*
* Example:
* var filter = {
* name: 'TestDI',
* }
* @returns {Promise<object[] | string[]> } - List of API.CONFIG.SIGNAL.Signal instances
* @todo: <br> (boolean) invert inverted signals -- NOT WORKING
*/
this.search = async function (filter = {}, onlyName = false) {
function isValidType(type) {
const validTypes = ['DI', 'DO', 'AI', 'AO', 'GI', 'GO'];
if (validTypes.includes(type)) {
return true;
}
if (type.startsWith('[') && type.endsWith(']')) {
const array = type.slice(1, -1).split(',');
return array.every((item) => validTypes.includes(item));
}
return false;
}
try {
let f = {};
// pass from filter to f only the keys that are not empty strings or booleans (either true or false)
Object.keys(filter).forEach((key) => {
const keys = ['name', 'device', 'network', 'category', 'type', 'blocked'];
if (keys.includes(key) && filter[key]) {
f[key] = filter[key];
}
});
if (f.type && !isValidType(f.type)) {
return API.rejectWithStatus(`Invalid type ${f.type}`, {}, {errorCode: ErrorCode.InvalidSignalType});
}
var ss = await RWS.IO.searchSignals(f);
const names = ss.map((s) => s.getName());
if (onlyName)
return ss.map((s) => {
return s.getName();
});
const signals = [];
for (var i = 0; i < ss.length; i++) {
signals.push(await this.getSignal(ss[i].getName()));
}
return signals;
} catch (e) {
return API.rejectWithStatus('Failed to serach signals', e, {errorCode: ErrorCode.FailedToSearchSignals});
}
};
this.searchByName = async (name) => {
const signals = await this.search({name: name});
return signals.length === 1 ? signals[0] : signals;
};
this.searchByCategory = async (category) => {
const signals = await this.search({category: category});
return signals.length === 1 ? signals[0] : signals;
};
this.searchByType = async (type, device = null) => {
if (Array.isArray(type)) {
const t = type.join(',');
type = `[${t}]`;
}
return device === null ? await this.search({type: type}) : await this.search({type: type, device: device});
};
this.isAnySignalModified = async () => {
return this.signals.some((signal) => signal.modified === true);
};
})();
cfg.constructedCfg = true;
};
if (typeof API.constructedCfg === 'undefined') {
factoryApiCfg(API);
}
export default API;
export {factoryApiCfg};