- "use strict";
-
- /**
- * @module opcua.address_space
- */
-
- const util = require("util");
- const assert = require("node-opcua-assert").assert;
- const _ = require("underscore");
-
- const NodeId = require("node-opcua-nodeid").NodeId;
- const DataType = require("node-opcua-variant").DataType;
- const Variant = require("node-opcua-variant").Variant;
-
- const coerceLocalizedText = require("node-opcua-data-model").coerceLocalizedText;
- const StatusCodes = require("node-opcua-status-code").StatusCodes;
-
- const AttributeIds = require("node-opcua-data-model").AttributeIds;
-
- const UAObjectType = require("../ua_object_type").UAObjectType;
- const UAObject = require("../ua_object").UAObject;
- const BaseNode = require("../base_node").BaseNode;
-
- const utils= require("node-opcua-utils");
-
- const doDebug = false;
- /*
- *
- * @class UAStateMachine
- * @constructor
- * @extends UAObject
- * UAStateMachine.initialState as Node
- *
- */
- function UAStateMachine() {
-
- /**
- * @property currentState
- */
- }
- util.inherits(UAStateMachine,UAObject);
-
-
- UAStateMachine.promote = function( node) {
- if (node instanceof UAStateMachine) {
- return node; // already promoted
- }
- Object.setPrototypeOf(node,UAStateMachine.prototype);
- node._post_initialize();
- return node;
- };
-
- UAStateMachine.prototype._post_initialize = function() {
- const self =this;
- const addressSpace = self.addressSpace;
- const finiteStateMachineType = addressSpace.findObjectType("FiniteStateMachineType");
- assert(finiteStateMachineType.browseName.toString() === "FiniteStateMachineType");
-
- assert(self.typeDefinitionObj&&!self.subtypeOfObj);
- assert(!self.typeDefinitionObj || self.typeDefinitionObj.isSupertypeOf(finiteStateMachineType));
- // get current Status
-
- const d = self.currentState.readValue();
-
- if (d.statusCode !== StatusCodes.Good) {
- self.setState(null);
- } else {
- self.currentStateNode = self.getStateByName(d.value.value.text.toString());
- }
-
- };
-
-
- function getComponentFromTypeAndSubtype(typeDef) {
-
- const components_parts = [];
- components_parts.push(typeDef.getComponents());
-
- while(typeDef.subtypeOfObj) {
- typeDef = typeDef.subtypeOfObj;
- components_parts.push(typeDef.getComponents());
- }
- return [].concat.apply([],components_parts);
- }
-
- /**
- * @method getStates
- * @return {*}
- */
- UAStateMachine.prototype.getStates = function() {
-
- const self = this;
- const addressSpace = self.addressSpace;
-
- const initialStateType = addressSpace.findObjectType("InitialStateType");
- const stateType = addressSpace.findObjectType("StateType");
-
- assert(initialStateType.isSupertypeOf(stateType));
-
- const typeDef = self.typeDefinitionObj;
-
- let comp = getComponentFromTypeAndSubtype(typeDef);
-
- comp = comp.filter(function(c){
- if (!(c.typeDefinitionObj instanceof UAObjectType)) {
- return false;
- }
- return c.typeDefinitionObj.isSupertypeOf(stateType);
- });
-
- return comp;
- };
-
- UAStateMachine.prototype.__defineGetter__("states",function() {
- return this.getStates();
- });
-
- /**
- * @method getStateByName
- * @param name {string}
- * @return {null|UAObject}
- */
- UAStateMachine.prototype.getStateByName = function(name) {
-
- const self = this;
- let states = self.getStates();
- states = states.filter(function(s){ return s.browseName.name === name; });
- assert(states.length<=1);
- return states.length === 1 ? states[0] : null;
- };
-
-
- UAStateMachine.prototype.getTransitions = function() {
-
- const self = this;
- const addressSpace = self.addressSpace;
-
- const transitionType = addressSpace.findObjectType("TransitionType");
- const typeDef = self.typeDefinitionObj;
-
- let comp = getComponentFromTypeAndSubtype(typeDef);
-
- comp = comp.filter(function(c){
- if (!(c.typeDefinitionObj instanceof UAObjectType)) {
- return false;
- }
- return c.typeDefinitionObj.isSupertypeOf(transitionType);
- });
-
- return comp;
-
- };
- UAStateMachine.prototype.__defineGetter__("transitions",function() {
- return this.getTransitions();
- });
-
- /**
- * return the node InitialStateType
- * @property initialState
- * @type {UAObject}
- */
- UAStateMachine.prototype.__defineGetter__("initialState", function() {
- const self = this;
- const addressSpace = self.addressSpace;
-
- const initialStateType = addressSpace.findObjectType("InitialStateType");
- const typeDef = self.typeDefinitionObj;
-
- let comp = getComponentFromTypeAndSubtype(typeDef);
-
- comp = comp.filter(function(c){
- return c.typeDefinitionObj === initialStateType;
- });
-
- // istanbul ignore next
- if (comp.length >1 ) {
- throw new Error(" More than 1 initial state in stateMachine");
- }
- return comp.length === 0 ? null : comp[0];
- });
-
-
- UAStateMachine.prototype._coerceNode = function(node) {
-
- if (node === null) {
- return null;
- }
- const self = this;
- const addressSpace = self.addressSpace;
- let retValue = node;
- if (node instanceof BaseNode) {
- return node;
- } else if(node instanceof NodeId) {
- retValue = addressSpace.findNode(node);
-
- } else if (_.isString(node)) {
- retValue = self.getStateByName(node);
- }
- if (!retValue) {
- ///xx console.log(" cannot find component with ",node ? node.toString():"null");
- }
- return retValue;
- };
-
-
- UAObject.prototype.__defineGetter__("toStateNode",function() {
- const self = this;
- const nodes = self.findReferencesAsObject("ToState",true);
- assert(nodes.length<=1);
- return nodes.length === 1 ? nodes[0] : null;
- });
-
- UAObject.prototype.__defineGetter__("fromStateNode",function() {
- const self =this;
- const nodes = self.findReferencesAsObject("FromState",true);
- assert(nodes.length<=1);
- return nodes.length === 1 ? nodes[0] : null;
- });
-
- /**
- * @method isValidTransition
- * @param toStateNode
- * @return {boolean}
- */
- UAStateMachine.prototype.isValidTransition = function(toStateNode) {
- assert(toStateNode);
- // is it legal to go from state currentState to toStateNode;
- const self = this;
- if (!self.currentStateNode) {
- return true;
- }
- const n = self.currentState.readValue();
-
- // to be executed there must be a transition from currentState to toState
- const transition = self.findTransitionNode(self.currentStateNode,toStateNode);
- if (!transition) {
-
- // istanbul ignore next
- if (doDebug) {
- console.log(" No transition from ",self.currentStateNode.browseName.toString(), " to " , toStateNode.toString());
- }
- return false;
- }
- return true;
- };
-
- /**
- * @method findTransitionNode
- * @param fromStateNode {NodeId|BaseNode|string}
- * @param toStateNode {NodeId|BaseNode|string}
- * @return {UAObject}
- */
- UAStateMachine.prototype.findTransitionNode = function(fromStateNode,toStateNode) {
-
- const self = this;
- const addressSpace = self.addressSpace;
-
- fromStateNode = self._coerceNode(fromStateNode);
- if (!fromStateNode) { return null; }
-
- toStateNode = self._coerceNode(toStateNode);
-
- assert(fromStateNode instanceof UAObject);
- assert(toStateNode instanceof UAObject);
-
- const stateType = addressSpace.findObjectType("StateType");
-
- assert(fromStateNode.typeDefinitionObj.isSupertypeOf(stateType));
- assert(toStateNode.typeDefinitionObj.isSupertypeOf(stateType));
-
- let transitions = fromStateNode.findReferencesAsObject("FromState",false);
-
- transitions = transitions.filter(function(transition){
- assert(transition.toStateNode instanceof UAObject);
- return transition.toStateNode === toStateNode;
- });
- if (transitions.length ===0 ) {
- // cannot find a transition from fromState to toState
- return null;
- }
- assert(transitions.length === 1);
- return transitions[0];
- };
-
- UAStateMachine.prototype.__defineGetter__("currentStateNode",function() {
- const self = this;
- return self._currentStateNode;
- });
-
- /**
- * @property currentStateNode
- * @type BaseNode
- */
- UAStateMachine.prototype.__defineSetter__("currentStateNode",function(value) {
- const self = this;
- return self._currentStateNode = value;
- });
-
- /**
- * @method getCurrentState
- * @return {String}
- */
- UAStateMachine.prototype.getCurrentState = function() {
- //xx self.currentState.readValue().value.value.text
- //xx self.shelvingState.currentStateNode.browseName.toString()
- const self = this;
- if (!self.currentStateNode) {
- return null;
- }
- return self.currentStateNode.browseName.toString();
- };
-
- /**
- * @method setState
- * @param toStateNode {String|false|UAObject}
- */
- UAStateMachine.prototype.setState = function(toStateNode) {
-
- const self = this;
-
- if (!toStateNode) {
- self.currentStateNode = null;
- self.currentState.setValueFromSource({dataType: DataType.Null},StatusCodes.BadStateNotActive);
- return;
- }
- if (_.isString(toStateNode)) {
- const state= self.getStateByName(toStateNode);
- // istanbul ignore next
- if (!state) {
- throw new Error("Cannot find state with name "+toStateNode);
- }
- assert(state.browseName.toString() === toStateNode);
- toStateNode = state;
- }
- const fromStateNode = self.currentStateNode;
-
- toStateNode = self._coerceNode(toStateNode);
- assert(toStateNode instanceof UAObject);
-
- self.currentState.setValueFromSource({
- dataType: DataType.LocalizedText,
- value: coerceLocalizedText(toStateNode.browseName.toString())
- },StatusCodes.Good);
-
- self.currentStateNode = toStateNode;
-
- const transitionNode = self.findTransitionNode(fromStateNode,toStateNode);
-
- if (transitionNode) {
-
- //xx console.log("transitionNode ",transitionNode.toString());
- // The inherited Property SourceNode shall be filled with the NodeId of the StateMachine instance where the
- // Transition occurs. If the Transition occurs in a SubStateMachine, then the NodeId of the SubStateMachine
- // has to be used. If the Transition occurs between a StateMachine and a SubStateMachine, then the NodeId of
- // the StateMachine has to be used, independent of the direction of the Transition.
- // Transition identifies the Transition that triggered the Event.
- // FromState identifies the State before the Transition.
- // ToState identifies the State after the Transition.
- self.raiseEvent("TransitionEventType",{
-
- // Base EventType
- //xx nodeId: self.nodeId,
- // TransitionEventType
- // TransitionVariableType
- "transition": { dataType: "LocalizedText", value: transitionNode.displayName},
- "transition.id": transitionNode.transitionNumber.readValue().value,
-
- "fromState": { dataType: "LocalizedText", value: fromStateNode.displayName }, // StateVariableType
- "fromState.id": fromStateNode.stateNumber.readValue().value,
-
- "toState": { dataType: "LocalizedText", value: toStateNode.displayName }, // StateVariableType
- "toState.id": toStateNode.stateNumber.readValue().value
- });
-
- } else {
- if (fromStateNode && fromStateNode !== toStateNode) {
- if (doDebug) {
- const f = fromStateNode.browseName.toString();
- const t = toStateNode.browseName.toString();
- console.log("Warning".red, " cannot raise event : transition " + f + " to " + t + " is missing");
- }
- }
- }
-
- // also update executable flags on methods
-
- self.getMethods().forEach(function(method) {
- method._notifyAttributeChange(AttributeIds.Executable);
- });
-
- };
-
-
- exports.UAStateMachine = UAStateMachine;
-
-