APIs

Show:
  1. "use strict";
  2.  
  3. /**
  4. * @module opcua.address_space
  5. */
  6.  
  7. const util = require("util");
  8. const assert = require("node-opcua-assert").assert;
  9. const _ = require("underscore");
  10.  
  11. const NodeId = require("node-opcua-nodeid").NodeId;
  12. const DataType = require("node-opcua-variant").DataType;
  13. const Variant = require("node-opcua-variant").Variant;
  14.  
  15. const coerceLocalizedText = require("node-opcua-data-model").coerceLocalizedText;
  16. const StatusCodes = require("node-opcua-status-code").StatusCodes;
  17.  
  18. const AttributeIds = require("node-opcua-data-model").AttributeIds;
  19.  
  20. const UAObjectType = require("../ua_object_type").UAObjectType;
  21. const UAObject = require("../ua_object").UAObject;
  22. const BaseNode = require("../base_node").BaseNode;
  23.  
  24. const utils= require("node-opcua-utils");
  25.  
  26. const doDebug = false;
  27. /*
  28. *
  29. * @class UAStateMachine
  30. * @constructor
  31. * @extends UAObject
  32. * UAStateMachine.initialState as Node
  33. *
  34. */
  35. function UAStateMachine() {
  36.  
  37. /**
  38. * @property currentState
  39. */
  40. }
  41. util.inherits(UAStateMachine,UAObject);
  42.  
  43.  
  44. UAStateMachine.promote = function( node) {
  45. if (node instanceof UAStateMachine) {
  46. return node; // already promoted
  47. }
  48. Object.setPrototypeOf(node,UAStateMachine.prototype);
  49. node._post_initialize();
  50. return node;
  51. };
  52.  
  53. UAStateMachine.prototype._post_initialize = function() {
  54. const self =this;
  55. const addressSpace = self.addressSpace;
  56. const finiteStateMachineType = addressSpace.findObjectType("FiniteStateMachineType");
  57. assert(finiteStateMachineType.browseName.toString() === "FiniteStateMachineType");
  58.  
  59. assert(self.typeDefinitionObj&&!self.subtypeOfObj);
  60. assert(!self.typeDefinitionObj || self.typeDefinitionObj.isSupertypeOf(finiteStateMachineType));
  61. // get current Status
  62.  
  63. const d = self.currentState.readValue();
  64.  
  65. if (d.statusCode !== StatusCodes.Good) {
  66. self.setState(null);
  67. } else {
  68. self.currentStateNode = self.getStateByName(d.value.value.text.toString());
  69. }
  70.  
  71. };
  72.  
  73.  
  74. function getComponentFromTypeAndSubtype(typeDef) {
  75.  
  76. const components_parts = [];
  77. components_parts.push(typeDef.getComponents());
  78.  
  79. while(typeDef.subtypeOfObj) {
  80. typeDef = typeDef.subtypeOfObj;
  81. components_parts.push(typeDef.getComponents());
  82. }
  83. return [].concat.apply([],components_parts);
  84. }
  85.  
  86. /**
  87. * @method getStates
  88. * @return {*}
  89. */
  90. UAStateMachine.prototype.getStates = function() {
  91.  
  92. const self = this;
  93. const addressSpace = self.addressSpace;
  94.  
  95. const initialStateType = addressSpace.findObjectType("InitialStateType");
  96. const stateType = addressSpace.findObjectType("StateType");
  97.  
  98. assert(initialStateType.isSupertypeOf(stateType));
  99.  
  100. const typeDef = self.typeDefinitionObj;
  101. let comp = getComponentFromTypeAndSubtype(typeDef);
  102.  
  103. comp = comp.filter(function(c){
  104. if (!(c.typeDefinitionObj instanceof UAObjectType)) {
  105. return false;
  106. }
  107. return c.typeDefinitionObj.isSupertypeOf(stateType);
  108. });
  109.  
  110. return comp;
  111. };
  112.  
  113. UAStateMachine.prototype.__defineGetter__("states",function() {
  114. return this.getStates();
  115. });
  116.  
  117. /**
  118. * @method getStateByName
  119. * @param name {string}
  120. * @return {null|UAObject}
  121. */
  122. UAStateMachine.prototype.getStateByName = function(name) {
  123.  
  124. const self = this;
  125. let states = self.getStates();
  126. states = states.filter(function(s){ return s.browseName.name === name; });
  127. assert(states.length<=1);
  128. return states.length === 1 ? states[0] : null;
  129. };
  130.  
  131.  
  132. UAStateMachine.prototype.getTransitions = function() {
  133.  
  134. const self = this;
  135. const addressSpace = self.addressSpace;
  136.  
  137. const transitionType = addressSpace.findObjectType("TransitionType");
  138. const typeDef = self.typeDefinitionObj;
  139.  
  140. let comp = getComponentFromTypeAndSubtype(typeDef);
  141.  
  142. comp = comp.filter(function(c){
  143. if (!(c.typeDefinitionObj instanceof UAObjectType)) {
  144. return false;
  145. }
  146. return c.typeDefinitionObj.isSupertypeOf(transitionType);
  147. });
  148.  
  149. return comp;
  150.  
  151. };
  152. UAStateMachine.prototype.__defineGetter__("transitions",function() {
  153. return this.getTransitions();
  154. });
  155.  
  156. /**
  157. * return the node InitialStateType
  158. * @property initialState
  159. * @type {UAObject}
  160. */
  161. UAStateMachine.prototype.__defineGetter__("initialState", function() {
  162. const self = this;
  163. const addressSpace = self.addressSpace;
  164.  
  165. const initialStateType = addressSpace.findObjectType("InitialStateType");
  166. const typeDef = self.typeDefinitionObj;
  167.  
  168. let comp = getComponentFromTypeAndSubtype(typeDef);
  169.  
  170. comp = comp.filter(function(c){
  171. return c.typeDefinitionObj === initialStateType;
  172. });
  173.  
  174. // istanbul ignore next
  175. if (comp.length >1 ) {
  176. throw new Error(" More than 1 initial state in stateMachine");
  177. }
  178. return comp.length === 0 ? null : comp[0];
  179. });
  180.  
  181.  
  182. UAStateMachine.prototype._coerceNode = function(node) {
  183.  
  184. if (node === null) {
  185. return null;
  186. }
  187. const self = this;
  188. const addressSpace = self.addressSpace;
  189. let retValue = node;
  190. if (node instanceof BaseNode) {
  191. return node;
  192. } else if(node instanceof NodeId) {
  193. retValue = addressSpace.findNode(node);
  194.  
  195. } else if (_.isString(node)) {
  196. retValue = self.getStateByName(node);
  197. }
  198. if (!retValue) {
  199. ///xx console.log(" cannot find component with ",node ? node.toString():"null");
  200. }
  201. return retValue;
  202. };
  203.  
  204.  
  205. UAObject.prototype.__defineGetter__("toStateNode",function() {
  206. const self = this;
  207. const nodes = self.findReferencesAsObject("ToState",true);
  208. assert(nodes.length<=1);
  209. return nodes.length === 1 ? nodes[0] : null;
  210. });
  211.  
  212. UAObject.prototype.__defineGetter__("fromStateNode",function() {
  213. const self =this;
  214. const nodes = self.findReferencesAsObject("FromState",true);
  215. assert(nodes.length<=1);
  216. return nodes.length === 1 ? nodes[0] : null;
  217. });
  218.  
  219. /**
  220. * @method isValidTransition
  221. * @param toStateNode
  222. * @return {boolean}
  223. */
  224. UAStateMachine.prototype.isValidTransition = function(toStateNode) {
  225. assert(toStateNode);
  226. // is it legal to go from state currentState to toStateNode;
  227. const self = this;
  228. if (!self.currentStateNode) {
  229. return true;
  230. }
  231. const n = self.currentState.readValue();
  232.  
  233. // to be executed there must be a transition from currentState to toState
  234. const transition = self.findTransitionNode(self.currentStateNode,toStateNode);
  235. if (!transition) {
  236.  
  237. // istanbul ignore next
  238. if (doDebug) {
  239. console.log(" No transition from ",self.currentStateNode.browseName.toString(), " to " , toStateNode.toString());
  240. }
  241. return false;
  242. }
  243. return true;
  244. };
  245.  
  246. /**
  247. * @method findTransitionNode
  248. * @param fromStateNode {NodeId|BaseNode|string}
  249. * @param toStateNode {NodeId|BaseNode|string}
  250. * @return {UAObject}
  251. */
  252. UAStateMachine.prototype.findTransitionNode = function(fromStateNode,toStateNode) {
  253.  
  254. const self = this;
  255. const addressSpace = self.addressSpace;
  256.  
  257. fromStateNode = self._coerceNode(fromStateNode);
  258. if (!fromStateNode) { return null; }
  259.  
  260. toStateNode = self._coerceNode(toStateNode);
  261.  
  262. assert(fromStateNode instanceof UAObject);
  263. assert(toStateNode instanceof UAObject);
  264.  
  265. const stateType = addressSpace.findObjectType("StateType");
  266.  
  267. assert(fromStateNode.typeDefinitionObj.isSupertypeOf(stateType));
  268. assert(toStateNode.typeDefinitionObj.isSupertypeOf(stateType));
  269.  
  270. let transitions = fromStateNode.findReferencesAsObject("FromState",false);
  271.  
  272. transitions = transitions.filter(function(transition){
  273. assert(transition.toStateNode instanceof UAObject);
  274. return transition.toStateNode === toStateNode;
  275. });
  276. if (transitions.length ===0 ) {
  277. // cannot find a transition from fromState to toState
  278. return null;
  279. }
  280. assert(transitions.length === 1);
  281. return transitions[0];
  282. };
  283.  
  284. UAStateMachine.prototype.__defineGetter__("currentStateNode",function() {
  285. const self = this;
  286. return self._currentStateNode;
  287. });
  288.  
  289. /**
  290. * @property currentStateNode
  291. * @type BaseNode
  292. */
  293. UAStateMachine.prototype.__defineSetter__("currentStateNode",function(value) {
  294. const self = this;
  295. return self._currentStateNode = value;
  296. });
  297.  
  298. /**
  299. * @method getCurrentState
  300. * @return {String}
  301. */
  302. UAStateMachine.prototype.getCurrentState = function() {
  303. //xx self.currentState.readValue().value.value.text
  304. //xx self.shelvingState.currentStateNode.browseName.toString()
  305. const self = this;
  306. if (!self.currentStateNode) {
  307. return null;
  308. }
  309. return self.currentStateNode.browseName.toString();
  310. };
  311.  
  312. /**
  313. * @method setState
  314. * @param toStateNode {String|false|UAObject}
  315. */
  316. UAStateMachine.prototype.setState = function(toStateNode) {
  317.  
  318. const self = this;
  319.  
  320. if (!toStateNode) {
  321. self.currentStateNode = null;
  322. self.currentState.setValueFromSource({dataType: DataType.Null},StatusCodes.BadStateNotActive);
  323. return;
  324. }
  325. if (_.isString(toStateNode)) {
  326. const state= self.getStateByName(toStateNode);
  327. // istanbul ignore next
  328. if (!state) {
  329. throw new Error("Cannot find state with name "+toStateNode);
  330. }
  331. assert(state.browseName.toString() === toStateNode);
  332. toStateNode = state;
  333. }
  334. const fromStateNode = self.currentStateNode;
  335.  
  336. toStateNode = self._coerceNode(toStateNode);
  337. assert(toStateNode instanceof UAObject);
  338.  
  339. self.currentState.setValueFromSource({
  340. dataType: DataType.LocalizedText,
  341. value: coerceLocalizedText(toStateNode.browseName.toString())
  342. },StatusCodes.Good);
  343.  
  344. self.currentStateNode = toStateNode;
  345.  
  346. const transitionNode = self.findTransitionNode(fromStateNode,toStateNode);
  347.  
  348. if (transitionNode) {
  349.  
  350. //xx console.log("transitionNode ",transitionNode.toString());
  351. // The inherited Property SourceNode shall be filled with the NodeId of the StateMachine instance where the
  352. // Transition occurs. If the Transition occurs in a SubStateMachine, then the NodeId of the SubStateMachine
  353. // has to be used. If the Transition occurs between a StateMachine and a SubStateMachine, then the NodeId of
  354. // the StateMachine has to be used, independent of the direction of the Transition.
  355. // Transition identifies the Transition that triggered the Event.
  356. // FromState identifies the State before the Transition.
  357. // ToState identifies the State after the Transition.
  358. self.raiseEvent("TransitionEventType",{
  359.  
  360. // Base EventType
  361. //xx nodeId: self.nodeId,
  362. // TransitionEventType
  363. // TransitionVariableType
  364. "transition": { dataType: "LocalizedText", value: transitionNode.displayName},
  365. "transition.id": transitionNode.transitionNumber.readValue().value,
  366.  
  367. "fromState": { dataType: "LocalizedText", value: fromStateNode.displayName }, // StateVariableType
  368. "fromState.id": fromStateNode.stateNumber.readValue().value,
  369.  
  370. "toState": { dataType: "LocalizedText", value: toStateNode.displayName }, // StateVariableType
  371. "toState.id": toStateNode.stateNumber.readValue().value
  372. });
  373.  
  374. } else {
  375. if (fromStateNode && fromStateNode !== toStateNode) {
  376. if (doDebug) {
  377. const f = fromStateNode.browseName.toString();
  378. const t = toStateNode.browseName.toString();
  379. console.log("Warning".red, " cannot raise event : transition " + f + " to " + t + " is missing");
  380. }
  381. }
  382. }
  383.  
  384. // also update executable flags on methods
  385.  
  386. self.getMethods().forEach(function(method) {
  387. method._notifyAttributeChange(AttributeIds.Executable);
  388. });
  389.  
  390. };
  391.  
  392.  
  393. exports.UAStateMachine = UAStateMachine;
  394.