ecosystem-signal.js

// used to append interfaces to omnicore sdk so that created app and AppStudio can use
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';
import {Signal, Pending, Success} from '../store/const.js';
import Store from '../store/store.js';

const factoryApiSignalMonitor = function (es) {
  const logModule = 'ecosystem-signal';

  /**
   * The API.SIGNALMONITOR namespace provides a set of interfaces for easily and quickly adds signal monitoring and processing transactions.
   * @alias API.SIGNALMONITOR
   * @namespace
   */
  es.SIGNALMONITOR = new (function () {
    const subscribedSignals = {};
    const UNASSIGNED_TAG = '%%UNASSIGNED%%';

    const subscribeRes = async function (signal, func) {
      if (es.isSubscriptionBlocked) {
        Logger.w(WarningType.RWSSubscriptionBlocked, 'API.Signal: Subscription disabled when trying to monitor signal');
        return;
      }
      signal.addCallbackOnChanged(func);
      await signal.subscribe();
    };

    this.unsubscribeRes = async function (mappingKey) {
      const signalInfo = subscribedSignals[mappingKey];
      if (signalInfo && signalInfo.signal) {
        signalInfo.signal.unsubscribe();
        delete subscribedSignals[mappingKey];
      }
    };

    /**
     * Subscribes to a signal.
     * @alias monitorSignal
     * @memberof API.SIGNALMONITOR
     * @param {object} [variableObj] The signal attributes including the signal name and other necessary information.
     * @param {function} [callback] The callback function that is called when the signal changes.
     *
     * @example
     * API.SIGNALMONITOR.monitorSignal(
     *  {type: 'digitalsignal', name: 'ManualMode', network: 'Network1', device: 'Device1'},
     *  (v)=>{
     *    console.log(v);
     *  }
     * )
     */
    this.monitorSignal = async function (
      signalObj = {type: 'digitalsignal', network: '', device: '', name: 'ManualMode'},
      callback = null,
    ) {
      const mappingKey = Store.generateMappingKey(Signal, [
        signalObj.type,
        signalObj.network || UNASSIGNED_TAG,
        signalObj.device || UNASSIGNED_TAG,
        signalObj.name,
      ]);

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

      // 1.if the variable is not in the store, read its initial value and store it
      const {state, value} = Store.getNamespace(Signal).getMapping(mappingKey) || {state: undefined, value: undefined};
      if (state === undefined) {
        try {
          Store.getNamespace(Signal).setMapping(mappingKey, {
            state: Pending,
            value: undefined,
          });
          const initialValue = await API.RWS.SIGNAL.getSignalValue(signalObj.name, {
            deviceType: signalObj.device,
            networkType: signalObj.network,
          });

          Store.getNamespace(Signal).setMapping(mappingKey, {
            state: Success,
            value: initialValue,
          });
          typeof callback === 'function' && callback(initialValue);
          es._events.trigger(mappingKey + '_fetch', initialValue);
        } catch (error) {
          return API.rejectWithStatus(`Failed to read signal ${signalObj.name}'s value before subscription.`, error, {
            errorCode: ErrorCode.FailedToGetSignalValue,
          });
        }
      } else if (state === Pending) {
        es._events.once(mappingKey + '_fetch', (v) => {
          typeof callback === 'function' && callback(v);
        });
      } else {
        typeof callback === 'function' && callback(value);
      }

      if (es.isSubscriptionBlocked) {
        Logger.w(
          WarningType.RWSSubscriptionBlocked,
          `API.SIGNALMONITOR: Subscription disabled when trying to monitor signal ${mappingKey}`,
        );
        return;
      }

      // 2.if the variable is not subscribed yet, subscribe to it
      es._events.on(mappingKey, callback);
      if (!subscribedSignals[mappingKey]) {
        try {
          subscribedSignals[mappingKey] = {
            state: Pending,
          };
          const cbDigitalSignal = function (data) {
            const validValue = JSON.parse(data.toString() || -1);
            Store.getNamespace(Signal).setMapping(mappingKey, {
              state: Success,
              value: validValue,
            });
            es._events.trigger(mappingKey, validValue);
            Logger.i(InfoType.RobotOperation, `Triggered signal value:${validValue}. The new value is ${validValue}`);
          };

          const signalData = await API.SIGNAL.getSignal(
            signalObj.name,
            signalObj.network || UNASSIGNED_TAG,
            signalObj.device || UNASSIGNED_TAG,
          );
          await subscribeRes(signalData.signal, cbDigitalSignal.bind(this));
          subscribedSignals[mappingKey].state = Success;
          subscribedSignals[mappingKey].signal = signalData.signal;

          Logger.i(InfoType.RobotOperation, `Signal subscribed:${mappingKey}`);
        } catch (e) {
          return API.rejectWithStatus(`Failed to subscribe signal ${signalObj.name}`, e, {
            errorCode: ErrorCode.FailedToSubscribeSignal,
          });
        }
      }
    };

    /**
     * Subscribes to a digital signal.
     * @deprecated Please use this.monitorSignal instead
     * @alias monitorDigitalSignal
     * @memberof API.SIGNALMONITOR
     * @param {object} [variableObj] The signal attributes including the signal name and other necessary information.
     * @param {function} [callback] The callback function that is called when the signal changes.
     *
     * @example
     * API.SIGNALMONITOR.monitorDigitalSignal(
     *  {type: 'digitalsignal', name: 'ManualMode'},
     *  (v)=>{
     *    console.log(v);
     *  }
     * )
     */
    this.monitorDigitalSignal = this.monitorSignal;
  })();

  es.constructedSignalMonitor = true;
};

if (typeof API.constructedSignalMonitor === 'undefined') {
  factoryApiSignalMonitor(API);
}

export default API;
export {factoryApiSignalMonitor};