APIs

Show:
  1. "use strict";
  2. /**
  3. * @module opcua.address_space.AlarmsAndConditions
  4. */
  5.  
  6. require("set-prototype-of");
  7. const EventEmitter = require("events").EventEmitter;
  8. const util = require("util");
  9. const assert = require("node-opcua-assert").assert;
  10. const _ = require("underscore");
  11.  
  12. const UAVariable = require("../ua_variable").UAVariable;
  13. const Variant = require("node-opcua-variant").Variant;
  14. const DataType = require("node-opcua-variant").DataType;
  15. const StatusCodes = require("node-opcua-status-code").StatusCodes;
  16. const StatusCode = require("node-opcua-status-code").StatusCode;
  17. const UAObjectType = require("../ua_object_type").UAObjectType;
  18. const UAObject = require("../ua_object").UAObject;
  19. const BaseNode = require("../base_node").BaseNode;
  20. const AttributeIds = require("node-opcua-data-model").AttributeIds;
  21. const NodeClass = require("node-opcua-data-model").NodeClass;
  22. const TimeZone = require("node-opcua-data-model").TimeZone;
  23. const UAStateMachine = require("../state_machine/finite_state_machine").UAStateMachine;
  24. const UATwoStateVariable = require("../ua_two_state_variable").UATwoStateVariable;
  25.  
  26. const resolveNodeId = require("node-opcua-nodeid").resolveNodeId;
  27. const coerceLocalizedText = require("node-opcua-data-model").coerceLocalizedText;
  28. const LocalizedText = require("node-opcua-data-model").LocalizedText;
  29. const NodeId = require("node-opcua-nodeid").NodeId;
  30.  
  31. const EventData = require("../address_space_add_event_type").EventData;
  32.  
  33. const debugLog = require("node-opcua-debug").make_debugLog(__filename);
  34. const doDebug = require("node-opcua-debug").checkDebugFlag(__filename);
  35.  
  36. const AddressSpace = require("../address_space").AddressSpace;
  37. const Namespace = require("../namespace").Namespace;
  38.  
  39. const SessionContext = require("../session_context").SessionContext;
  40.  
  41. const utils = require("node-opcua-utils");
  42.  
  43. function _visit(self, node, prefix) {
  44. const aggregates = node.getAggregates();
  45.  
  46. aggregates.forEach(function(aggregate) {
  47. if (aggregate instanceof UAVariable) {
  48. let name = aggregate.browseName.toString();
  49. name = utils.lowerFirstLetter(name);
  50.  
  51. const key = prefix + name;
  52.  
  53. // istanbul ignore next
  54. if (doDebug) {
  55. debugLog("adding key =", key);
  56. }
  57. self._map[key] = aggregate.readValue().value;
  58. self._node_index[key] = aggregate;
  59. _visit(self, aggregate, prefix + name + ".");
  60. }
  61. });
  62. }
  63. function _installOnChangeEventHandlers(self, node, prefix) {
  64. const aggregates = node.getAggregates();
  65.  
  66. aggregates.forEach(function(aggregate) {
  67. if (aggregate instanceof UAVariable) {
  68. let name = aggregate.browseName.toString();
  69. name = utils.lowerFirstLetter(name);
  70.  
  71. const key = prefix + name;
  72.  
  73. // istanbul ignore next
  74. if (doDebug) {
  75. debugLog("adding key =", key);
  76. }
  77.  
  78. aggregate.on("value_changed", function(newDataValue, oldDataValue) {
  79. self._map[key] = newDataValue.value;
  80. self._node_index[key] = aggregate;
  81. });
  82.  
  83. _installOnChangeEventHandlers(self, aggregate, prefix + name + ".");
  84. }
  85. });
  86. }
  87. function _ensure_condition_values_correctness(self, node, prefix, error) {
  88. const displayError = !!error;
  89. error = error || [];
  90.  
  91. const aggregates = node.getAggregates();
  92.  
  93. aggregates.forEach(function(aggregate) {
  94. if (aggregate instanceof UAVariable) {
  95. let name = aggregate.browseName.toString();
  96. name = utils.lowerFirstLetter(name);
  97.  
  98. const key = prefix + name;
  99.  
  100. const snapshot_value = self._map[key].toString();
  101. const condition_value = aggregate.readValue().value.toString();
  102.  
  103. if (snapshot_value !== condition_value) {
  104. error.push(
  105. " Condition Branch0 is not in sync with node values for " +
  106. key +
  107. "\n v1= " +
  108. snapshot_value +
  109. "\n v2= " +
  110. condition_value
  111. );
  112. }
  113.  
  114. self._node_index[key] = aggregate;
  115. _ensure_condition_values_correctness(self, aggregate, prefix + name + ".", error);
  116. }
  117. });
  118. if (displayError && error.length) {
  119. throw new Error(error.join("\n"));
  120. }
  121. }
  122. function _record_condition_state(self, condition) {
  123. self._map = {};
  124. self._node_index = {};
  125. assert(condition instanceof UAConditionBase);
  126. _visit(self, condition, "");
  127. }
  128. /**
  129. * @class ConditionSnapshot
  130. * @extends EventEmitter
  131. * @param condition
  132. * @param branchId
  133. * @constructor
  134. */
  135. function ConditionSnapshot(condition, branchId) {
  136. const self = this;
  137. EventEmitter.call(this);
  138. if (condition && branchId) {
  139. assert(branchId instanceof NodeId);
  140. //xx self.branchId = branchId;
  141. self.condition = condition;
  142. self.eventData = new EventData(condition);
  143. // a nodeId/Variant map
  144. _record_condition_state(self, condition);
  145.  
  146. if (branchId === NodeId.NullNodeId) {
  147. _installOnChangeEventHandlers(self, condition, "");
  148. }
  149.  
  150. self._set_var("branchId", DataType.NodeId, branchId);
  151. }
  152. }
  153. util.inherits(ConditionSnapshot, EventEmitter);
  154.  
  155. // /**
  156. // *
  157. // * @return {ConditionSnapshot}
  158. // */
  159. // ConditionSnapshot.prototype.clone = function () {
  160. // var self = this;
  161. // var clone = new ConditionSnapshot();
  162. // clone.branchId = self.branchId;
  163. // clone.condition = self.condition;
  164. // //xx clone.eventData = new EventData(clone.condition);
  165. // clone._map = _.clone(self._map);
  166. // return clone;
  167. // };
  168.  
  169. const disabledVar = new Variant({
  170. dataType: "StatusCode",
  171. value: StatusCodes.BadConditionDisabled
  172. });
  173.  
  174. ConditionSnapshot.prototype._constructEventData = function() {
  175. const self = this;
  176. const addressSpace = self.condition.addressSpace;
  177.  
  178. if (self.branchId === NodeId.NullNodeId) {
  179. _ensure_condition_values_correctness(self, self.condition, "");
  180. }
  181.  
  182. const isDisabled = !self.condition.getEnabledState();
  183. const eventData = new EventData(self.condition);
  184. Object.keys(self._map).forEach(function(key) {
  185. const node = self._node_index[key];
  186. if (isDisabled && !_varTable.hasOwnProperty(key)) {
  187. eventData.setValue(key, node, disabledVar);
  188. } else {
  189. eventData.setValue(key, node, self._map[key]);
  190. }
  191. });
  192.  
  193. return eventData;
  194.  
  195. // self.condition.getAggregates().forEach(function(child){
  196. // if (child instanceof UAVariable) {
  197. // var name = utils.lowerFirstLetter(child.browseName.toString());
  198. // self.eventData[name] =child.readValue().value;
  199. // }
  200. // });
  201. // return self.eventData.clone();
  202. };
  203.  
  204. /**
  205. * @method resolveSelectClause
  206. * @param selectClause {SelectClause}
  207. */
  208. ConditionSnapshot.prototype.resolveSelectClause = function(selectClause) {
  209. const self = this;
  210. return self.eventData.resolveSelectClause(selectClause);
  211. };
  212.  
  213. /**
  214. * @method readValue
  215. * @param nodeId {NodeId}
  216. * @param selectClause {SelectClause}
  217. * @return {Variant}
  218. */
  219. ConditionSnapshot.prototype.readValue = function(nodeId, selectClause) {
  220. const self = this;
  221.  
  222. const isDisabled = !self.condition.getEnabledState();
  223. if (isDisabled) {
  224. return disabledVar;
  225. }
  226.  
  227. const key = nodeId.toString();
  228. const variant = self._map[key];
  229. if (!variant) {
  230. // the value is not handled by us .. let's delegate
  231. // to the eventData helper object
  232. return self.eventData.readValue(nodeId, selectClause);
  233. }
  234. assert(variant instanceof Variant);
  235. return variant;
  236. };
  237.  
  238. function normalizeName(str) {
  239. return str
  240. .split(".")
  241. .map(utils.lowerFirstLetter)
  242. .join(".");
  243. }
  244. ConditionSnapshot.normalizeName = normalizeName;
  245.  
  246. // list of Condition variables that should not be published as BadConditionDisabled when the condition
  247. // is in a disabled state.
  248. var _varTable = {
  249. branchId: 1,
  250. eventId: 1,
  251. eventType: 1,
  252. sourceNode: 1,
  253. sourceName: 1,
  254. time: 1,
  255. enabledState: 1,
  256. "enabledState.id": 1,
  257. "enabledState.effectiveDisplayName": 1,
  258. "enabledState.transitionTime": 1,
  259. conditionClassId: 1,
  260. conditionClassName: 1,
  261. conditionName: 1
  262. };
  263. ConditionSnapshot.prototype._get_var = function(varName, dataType) {
  264. const self = this;
  265.  
  266. if (!self.condition.getEnabledState() && !_varTable.hasOwnProperty(varName)) {
  267. console.log("ConditionSnapshot#_get_var condition enabled =", self.condition.getEnabledState());
  268. return disabledVar;
  269. }
  270.  
  271. const key = normalizeName(varName);
  272. const variant = self._map[key];
  273. return variant.value;
  274. };
  275.  
  276. ConditionSnapshot.prototype._set_var = function(varName, dataType, value) {
  277. const self = this;
  278.  
  279. const key = normalizeName(varName);
  280. // istanbul ignore next
  281. if (!self._map.hasOwnProperty(key)) {
  282. if (doDebug) {
  283. debugLog(" cannot find node ".white.bold.bgRed + varName.cyan);
  284. debugLog(" map=", Object.keys(self._map).join(" "));
  285. }
  286. }
  287. self._map[key] = new Variant({
  288. dataType: dataType,
  289. value: value
  290. });
  291.  
  292. if (self._map[key + ".sourceTimestamp"]) {
  293. self._map[key + ".sourceTimestamp"] = new Variant({
  294. dataType: DataType.DateTime,
  295. value: new Date()
  296. });
  297. }
  298.  
  299. const variant = self._map[key];
  300. const node = self._node_index[key];
  301. assert(node instanceof UAVariable);
  302. self.emit("value_changed", node, variant);
  303. };
  304.  
  305. /**
  306. * @method getBrandId
  307. * @return {NodeId}
  308. */
  309. ConditionSnapshot.prototype.getBranchId = function() {
  310. const self = this;
  311. return self._get_var("branchId", DataType.NodeId);
  312. };
  313.  
  314. /**
  315. * @method getEventId
  316. * @return {ByteString}
  317. */
  318. ConditionSnapshot.prototype.getEventId = function() {
  319. const self = this;
  320. return self._get_var("eventId", DataType.ByteString);
  321. };
  322. /**
  323. * @method getRetain
  324. * @return {Boolean}
  325. */
  326. ConditionSnapshot.prototype.getRetain = function() {
  327. const self = this;
  328. return self._get_var("retain", DataType.Boolean);
  329. };
  330.  
  331. /**
  332. *
  333. * @method setRetain
  334. * @param retainFlag {Boolean}
  335. */
  336. ConditionSnapshot.prototype.setRetain = function(retainFlag) {
  337. const self = this;
  338. retainFlag = !!retainFlag;
  339. return self._set_var("retain", DataType.Boolean, retainFlag);
  340. };
  341.  
  342. /**
  343. * @method renewEventId
  344. *
  345. */
  346. ConditionSnapshot.prototype.renewEventId = function() {
  347. const self = this;
  348. const addressSpace = self.condition.addressSpace;
  349. // create a new event Id for this new condition
  350. const eventId = addressSpace.generateEventId();
  351. const ret = self._set_var("eventId", DataType.ByteString, eventId.value);
  352.  
  353. //xx var branch = self; console.log("MMMMMMMMrenewEventId branch " + branch.getBranchId().toString() + " eventId = " + branch.getEventId().toString("hex"));
  354.  
  355. return ret;
  356. };
  357.  
  358. /**
  359. * @method getEnabledState
  360. * @return {Boolean}
  361. */
  362. ConditionSnapshot.prototype.getEnabledState = function() {
  363. const self = this;
  364. return self._get_twoStateVariable("enabledState");
  365. };
  366. /**
  367. * @method setEnabledState
  368. * @param value {Boolean}
  369. * @return void
  370. */
  371. ConditionSnapshot.prototype.setEnabledState = function(value) {
  372. const self = this;
  373. return self._set_twoStateVariable("enabledState", value);
  374. };
  375. /**
  376. * @method getEnabledStateAsString
  377. * @return {String}
  378. */
  379. ConditionSnapshot.prototype.getEnabledStateAsString = function() {
  380. const self = this;
  381. return self._get_var("enabledState", DataType.LocalizedText).text;
  382. };
  383.  
  384. /**
  385. * @method getComment
  386. * @return {LocalizedText}
  387. */
  388. ConditionSnapshot.prototype.getComment = function() {
  389. const self = this;
  390. return self._get_var("comment", DataType.LocalizedText);
  391. };
  392.  
  393. /**
  394. * Set condition comment
  395. *
  396. * Comment contains the last comment provided for a certain state (ConditionBranch). It may
  397. * have been provided by an AddComment Method, some other Method or in some other
  398. * manner. The initial value of this Variable is null, unless it is provided in some other manner. If
  399. * a Method provides as an option the ability to set a Comment, then the value of this Variable is
  400. * reset to null if an optional comment is not provided.
  401. *
  402. * @method setComment
  403. * @param txtMessage {LocalizedText}
  404. */
  405. ConditionSnapshot.prototype.setComment = function(txtMessage) {
  406. const self = this;
  407. assert(txtMessage);
  408. txtMessage = coerceLocalizedText(txtMessage);
  409. self._set_var("comment", DataType.LocalizedText, txtMessage);
  410. /*
  411. * OPCUA Spec 1.0.3 - Part 9:
  412. * Comment, severity and quality are important elements of Conditions and any change
  413. * to them will cause Event Notifications.
  414. *
  415. */
  416. self._need_event_raise = true;
  417. };
  418.  
  419. /**
  420. *
  421. * @method setMessage
  422. * @param txtMessage {LocalizedText}
  423. */
  424. ConditionSnapshot.prototype.setMessage = function(txtMessage) {
  425. const self = this;
  426. assert(txtMessage);
  427. txtMessage = coerceLocalizedText(txtMessage);
  428. return self._set_var("message", DataType.LocalizedText, txtMessage);
  429. };
  430.  
  431. /**
  432. * @method setClientUserId
  433. * @param userIdentity {String}
  434. */
  435. ConditionSnapshot.prototype.setClientUserId = function(userIdentity) {
  436. const self = this;
  437. return self._set_var("clientUserId", DataType.String, userIdentity.toString());
  438. };
  439.  
  440. /*
  441. *
  442. *
  443. * as per spec 1.0.3 - Part 9
  444. *
  445. * Quality reveals the status of process values or other resources that this Condition instance is
  446. * based upon. If, for example, a process value is “Uncertain”, the associated “LevelAlarm”
  447. * Condition is also questionable. Values for the Quality can be any of the OPC StatusCodes
  448. * defined in Part 8 as well as Good, Uncertain and Bad as defined in Part 4. These
  449. * StatusCodes are similar to but slightly more generic than the description of data quality in the
  450. * various field bus specifications. It is the responsibility of the Server to map internal status
  451. * information to these codes. A Server which supports no quality information shall return Good.
  452. * This quality can also reflect the communication status associated with the system that this
  453. * value or resource is based on and from which this Alarm was received. For communication
  454. * errors to the underlying system, especially those that result in some unavailable Event fields,
  455. * the quality shall be Bad_NoCommunication error.
  456. *
  457. * Quality refers to the quality of the data value(s) upon which this Condition is based. Since a
  458. * Condition is usually based on one or more Variables, the Condition inherits the quality of
  459. * these Variables. E.g., if the process value is “Uncertain”, the “LevelAlarm” Condition is also
  460. * questionable. If more than one variable is represented by a given condition or if the condition
  461. * is from an underlining system and no direct mapping to a variable is available, it is up to the
  462. * application to determine what quality is displayed as part of the condition.
  463. */
  464.  
  465. /**
  466. * set the condition quality
  467. * @method setQuality
  468. * @param quality {StatusCode}
  469. */
  470. ConditionSnapshot.prototype.setQuality = function(quality) {
  471. const self = this;
  472. assert(quality instanceof StatusCode);
  473. assert(quality.hasOwnProperty("value") || "quality must be a StatusCode");
  474. self._set_var("quality", DataType.StatusCode, quality);
  475. /*
  476. * OPCUA Spec 1.0.3 - Part 9:
  477. * Comment, severity and quality are important elements of Conditions and any change
  478. * to them will cause Event Notifications.
  479. *
  480. */
  481. self._need_event_raise = true;
  482. };
  483.  
  484. /**
  485. * @method getQuality
  486. * @return {StatusCode}
  487. */
  488. ConditionSnapshot.prototype.getQuality = function() {
  489. const self = this;
  490. return self._get_var("quality", DataType.StatusCode);
  491. };
  492.  
  493. /*
  494. * as per spec 1.0.3 - Part 9
  495. * The Severity of a Condition is inherited from the base Event model defined in Part 5. It
  496. * indicates the urgency of the Condition and is also commonly called ‘priority’, especially in
  497. * relation to Alarms in the ProcessConditionClass.
  498. *
  499. * as per spec 1.0.3 - PArt 5
  500. * Severity is an indication of the urgency of the Event. This is also commonly called “priority”.
  501. * Values will range from 1 to 1 000, with 1 being the lowest severity and 1 000 being the highest.
  502. * Typically, a severity of 1 would indicate an Event which is informational in nature, while a value
  503. * of 1 000 would indicate an Event of catastrophic nature, which could potentially result in severe
  504. * financial loss or loss of life.
  505. * It is expected that very few Server implementations will support 1 000 distinct severity levels.
  506. * Therefore, Server developers are responsible for distributing their severity levels across the
  507. * 1 to 1 000 range in such a manner that clients can assume a linear distribution. For example, a
  508. * client wishing to present five severity levels to a user should be able to do the following
  509. * mapping:
  510. * Client Severity OPC Severity
  511. * HIGH 801 – 1 000
  512. * MEDIUM HIGH 601 – 800
  513. * MEDIUM 401 – 600
  514. * MEDIUM LOW 201 – 400
  515. * LOW 1 – 200
  516. * In many cases a strict linear mapping of underlying source severities to the OPC Severity range
  517. * is not appropriate. The Server developer will instead intelligently map the underlying source
  518. * severities to the 1 to 1 000 OPC Severity range in some other fashion. In particular, it is
  519. * recommended that Server developers map Events of high urgency into the OPC severity range
  520. * of 667 to 1 000, Events of medium urgency into the OPC severity range of 334 to 666 and
  521. * Events of low urgency into OPC severities of 1 to 333.
  522. */
  523. /**
  524. * @method setSeverity
  525. * @param severity {UInt16}
  526. */
  527. ConditionSnapshot.prototype.setSeverity = function(severity) {
  528. const self = this;
  529. assert(_.isFinite(severity), "expecting a UInt16");
  530.  
  531. // record automatically last severity
  532. const lastSeverity = self.getSeverity();
  533. self.setLastSeverity(lastSeverity);
  534. self._set_var("severity", DataType.UInt16, severity);
  535. /*
  536. * OPCUA Spec 1.0.3 - Part 9:
  537. * Comment, severity and quality are important elements of Conditions and any change
  538. * to them will cause Event Notifications.
  539. *
  540. */
  541. self._need_event_raise = true;
  542. };
  543.  
  544. /**
  545. * @method getSeverity
  546. * @return {UInt16}
  547. */
  548. ConditionSnapshot.prototype.getSeverity = function() {
  549. const self = this;
  550. assert(self.condition.getEnabledState(), "condition must be enabled");
  551. const value = self._get_var("severity", DataType.UInt16);
  552. return +value;
  553. };
  554.  
  555. /*
  556. * as per spec 1.0.3 - part 9:
  557. * LastSeverity provides the previous severity of the ConditionBranch. Initially this Variable
  558. * contains a zero value; it will return a value only after a severity change. The new severity is
  559. * supplied via the Severity Property which is inherited from the BaseEventType.
  560. *
  561. */
  562. /**
  563. * @method setLastSeverity
  564. * @param severity {UInt16}
  565. */
  566. ConditionSnapshot.prototype.setLastSeverity = function(severity) {
  567. const self = this;
  568. severity = +severity;
  569. return self._set_var("lastSeverity", DataType.UInt16, severity);
  570. };
  571. /**
  572. * @method getLastSeverity
  573. * @return {UInt16}
  574. */
  575. ConditionSnapshot.prototype.getLastSeverity = function() {
  576. const self = this;
  577. const value = self._get_var("lastSeverity", DataType.UInt16);
  578. return +value;
  579. };
  580.  
  581. /**
  582. * setReceiveTime
  583. *
  584. * (as per OPCUA 1.0.3 part 5)
  585. *
  586. * ReceiveTime provides the time the OPC UA Server received the Event from the underlying
  587. * device of another Server.
  588. *
  589. * ReceiveTime is analogous to ServerTimestamp defined in Part 4, i.e.
  590. * in the case where the OPC UA Server gets an Event from another OPC UA Server, each Server
  591. * applies its own ReceiveTime. That implies that a Client may get the same Event, having the
  592. * same EventId, from different Servers having different values of the ReceiveTime.
  593. *
  594. * The ReceiveTime shall always be returned as value and the Server is not allowed to return a
  595. * StatusCode for the ReceiveTime indicating an error.
  596. *
  597. * @method setReceiveTime
  598. * @param time {Date} : UTCTime
  599. */
  600. ConditionSnapshot.prototype.setReceiveTime = function(time) {
  601. assert(time instanceof Date);
  602. const self = this;
  603. return self._set_var("receiveTime", DataType.DateTime, time);
  604. };
  605.  
  606. /**
  607. * (as per OPCUA 1.0.3 part 5)
  608.  
  609. * Time provides the time the Event occurred. This value is set as close to the event generator as
  610. * possible. It often comes from the underlying system or device. Once set, intermediate OPC UA
  611. * Servers shall not alter the value.
  612. *
  613. * @method setTime
  614. * @param time {Date}
  615. */
  616. ConditionSnapshot.prototype.setTime = function(time) {
  617. assert(time instanceof Date);
  618. const self = this;
  619. return self._set_var("time", DataType.DateTime, time);
  620. };
  621.  
  622. /**
  623. * LocalTime is a structure containing the Offset and the DaylightSavingInOffset flag. The Offset
  624. * specifies the time difference (in minutes) between the Time Property and the time at the location
  625. * in which the event was issued. If DaylightSavingInOffset is TRUE, then Standard/Daylight
  626. * savings time (DST) at the originating location is in effect and Offset includes the DST c orrection.
  627. * If FALSE then the Offset does not include DST correction and DST may or may not have been
  628. * in effect.
  629. * @method setLocalTime
  630. * @param localTime {TimeZone}
  631. */
  632. ConditionSnapshot.prototype.setLocalTime = function(localTime) {
  633. assert(localTime instanceof TimeZone);
  634. const self = this;
  635. return self._set_var("localTime", DataType.ExtensionObject, new TimeZone(localTime));
  636. };
  637. // read only !
  638. ConditionSnapshot.prototype.getSourceName = function() {
  639. return this._get_var("sourceName", DataType.LocalizedText);
  640. };
  641.  
  642. /**
  643. * @method getSourceNode
  644. * return {NodeId}
  645. */
  646. ConditionSnapshot.prototype.getSourceNode = function() {
  647. return this._get_var("sourceNode", DataType.NodeId);
  648. };
  649.  
  650. /**
  651. * @method getEventType
  652. * return {NodeId}
  653. */
  654. ConditionSnapshot.prototype.getEventType = function() {
  655. return this._get_var("eventType", DataType.NodeId);
  656. };
  657.  
  658. /**
  659. * @method getMessage
  660. * return {LocalizedText}
  661. */
  662. ConditionSnapshot.prototype.getMessage = function() {
  663. return this._get_var("message", DataType.LocalizedText);
  664. };
  665.  
  666. ConditionSnapshot.prototype.isCurrentBranch = function() {
  667. return this._get_var("branchId") === NodeId.NullNodeId;
  668. };
  669.  
  670. /**
  671. * @class ConditionSnapshot
  672. * @param varName
  673. * @param value
  674. * @private
  675. */
  676. ConditionSnapshot.prototype._set_twoStateVariable = function(varName, value) {
  677. value = !!value;
  678. const self = this;
  679.  
  680. const hrKey = ConditionSnapshot.normalizeName(varName);
  681. const idKey = ConditionSnapshot.normalizeName(varName) + ".id";
  682.  
  683. const variant = new Variant({ dataType: DataType.Boolean, value: value });
  684. self._map[idKey] = variant;
  685.  
  686. // also change varName with human readable text
  687. const twoStateNode = self._node_index[hrKey];
  688. if (!twoStateNode) {
  689. throw new Error("Cannot find twoState Varaible with name " + varName);
  690. }
  691. if (!(twoStateNode instanceof UATwoStateVariable)) {
  692. throw new Error("Cannot find twoState Varaible with name " + varName + " " + twoStateNode);
  693. }
  694.  
  695. const txt = value ? twoStateNode._trueState : twoStateNode._falseState;
  696.  
  697. const hrValue = new Variant({
  698. dataType: DataType.LocalizedText,
  699. value: coerceLocalizedText(txt)
  700. });
  701. self._map[hrKey] = hrValue;
  702.  
  703. const node = self._node_index[idKey];
  704.  
  705. // also change ConditionNode if we are on currentBranch
  706. if (self.isCurrentBranch()) {
  707. assert(twoStateNode instanceof UATwoStateVariable);
  708. twoStateNode.setValue(value);
  709. //xx console.log("Is current branch", twoStateNode.toString(),variant.toString());
  710. //xx console.log(" = ",twoStateNode.getValue());
  711. }
  712. self.emit("value_changed", node, variant);
  713. };
  714.  
  715. ConditionSnapshot.prototype._get_twoStateVariable = function(varName) {
  716. const self = this;
  717. const key = ConditionSnapshot.normalizeName(varName) + ".id";
  718. const variant = self._map[key];
  719.  
  720. // istanbul ignore next
  721. if (!variant) {
  722. throw new Error("Cannot find TwoStateVariable with name " + varName);
  723. }
  724. return variant.value;
  725. };
  726. exports.ConditionSnapshot = ConditionSnapshot;
  727.  
  728. /**
  729. * @class BaseEventType
  730. * @class UAObject
  731. * @constructor
  732. */
  733. function BaseEventType() {}
  734. util.inherits(BaseEventType, UAObject);
  735.  
  736. /**
  737. * @method setSourceName
  738. * @param name
  739. */
  740. BaseEventType.prototype.setSourceName = function(name) {
  741. assert(typeof name === "string");
  742. const self = this;
  743. self.sourceName.setValueFromSource(
  744. new Variant({
  745. dataType: DataType.String,
  746. value: name
  747. })
  748. );
  749. };
  750.  
  751. /**
  752. * @method setSourceNode
  753. * @param node {NodeId|UAObject}
  754. */
  755. BaseEventType.prototype.setSourceNode = function(node) {
  756. const self = this;
  757. self.sourceNode.setValueFromSource(
  758. new Variant({
  759. dataType: DataType.NodeId,
  760. value: node.nodeId ? node.nodeId : node
  761. })
  762. );
  763. };
  764.  
  765. /**
  766. * @class UAConditionBase
  767. * @constructor
  768. * @extends BaseEventType
  769. */
  770. function UAConditionBase() {}
  771. util.inherits(UAConditionBase, BaseEventType);
  772. UAConditionBase.prototype.nodeClass = NodeClass.Object;
  773. UAConditionBase.typeDefinition = resolveNodeId("ConditionType");
  774.  
  775. /**
  776. * @method initialize
  777. * @private
  778. */
  779. UAConditionBase.prototype.initialize = function() {
  780. const self = this;
  781. self._branches = {};
  782. };
  783.  
  784. /**
  785. * @method post_initialize
  786. * @private
  787. */
  788. UAConditionBase.prototype.post_initialize = function() {
  789. const self = this;
  790. assert(!self._branch0);
  791. self._branch0 = new ConditionSnapshot(self, NodeId.NullNodeId);
  792.  
  793. // the condition OPCUA object alway reflects the default branch states
  794. // so we set a mechanism that automatically keeps self in sync
  795. // with the default branch.
  796.  
  797. // the implication of this convention is that interacting with the condition variable
  798. // shall be made by using branch0, any value change made
  799. // using the standard setValueFromSource mechanism will not be work properly.
  800. self._branch0.on("value_changed", function(node, variant) {
  801. assert(node instanceof UAVariable);
  802. node.setValueFromSource(variant);
  803. });
  804. };
  805.  
  806. /**
  807. * @method getBranchCount
  808. * @return {Number}
  809. */
  810. UAConditionBase.prototype.getBranchCount = function() {
  811. const self = this;
  812. return Object.keys(self._branches).length;
  813. };
  814. UAConditionBase.prototype.getBranches = function() {
  815. const self = this;
  816. return Object.keys(self._branches).map(function(x) {
  817. return self._branches[x];
  818. });
  819. };
  820. UAConditionBase.prototype.getBranchIds = function() {
  821. const self = this;
  822. return self.getBranches().map(function(b) {
  823. return b.getBranchId();
  824. });
  825. };
  826. const ec = require("node-opcua-basic-types");
  827. const randomGuid = ec.randomGuid;
  828. const makeNodeId = require("node-opcua-nodeid").makeNodeId;
  829.  
  830. function _create_new_branch_id() {
  831. return makeNodeId(randomGuid(), 1);
  832. }
  833.  
  834. /**
  835. * @method createBranch
  836. * @return {ConditionSnapshot}
  837. */
  838. UAConditionBase.prototype.createBranch = function() {
  839. const self = this;
  840. const branchId = _create_new_branch_id();
  841. const snapshot = new ConditionSnapshot(self, branchId);
  842. self._branches[branchId.toString()] = snapshot;
  843. return snapshot;
  844. };
  845. /**
  846. * @method deleteBranch
  847. * @param branch {ConditionSnapshot}
  848. */
  849. UAConditionBase.prototype.deleteBranch = function(branch) {
  850. const self = this;
  851. const key = branch.getBranchId().toString();
  852. assert(branch.getBranchId() !== NodeId.NullNodeId, "cannot delete branch zero");
  853. assert(self._branches.hasOwnProperty(key));
  854. delete self._branches[key];
  855. self.emit("branch_deleted", key);
  856. };
  857.  
  858. const minDate = new Date(1600, 1, 1);
  859.  
  860. function prepare_date(sourceTimestamp) {
  861. if (!sourceTimestamp || !sourceTimestamp.value) {
  862. return minDate;
  863. }
  864. assert(sourceTimestamp.value instanceof Date);
  865. return sourceTimestamp;
  866. }
  867.  
  868. function _update_sourceTimestamp(dataValue /*, indexRange*/) {
  869. const self = this;
  870. //xx console.log("_update_sourceTimestamp = "+self.nodeId.toString().cyan+ " " + self.browseName.toString(), self.sourceTimestamp.nodeId.toString().cyan + " " + dataValue.sourceTimestamp);
  871. self.sourceTimestamp.setValueFromSource({
  872. dataType: DataType.DateTime,
  873. value: dataValue.sourceTimestamp
  874. });
  875. }
  876. const makeAccessLevel = require("node-opcua-data-model").makeAccessLevel;
  877.  
  878. function _install_condition_variable_type(node) {
  879. assert(node instanceof BaseNode);
  880. // from spec 1.03 : 5.3 condition variables
  881. // However, a change in their value is considered important and supposed to trigger
  882. // an Event Notification. These information elements are called ConditionVariables.
  883. if (node.sourceTimestamp) {
  884. node.sourceTimestamp.accessLevel = makeAccessLevel("CurrentRead");
  885. } else {
  886. console.warn("cannot find node.sourceTimestamp", node.browseName.toString());
  887. }
  888. node.accessLevel = makeAccessLevel("CurrentRead");
  889.  
  890. // from spec 1.03 : 5.3 condition variables
  891. // a condition VariableType has a sourceTimeStamp exposed property
  892. // SourceTimestamp indicates the time of the last change of the Value of this ConditionVariable.
  893. // It shall be the same time that would be returned from the Read Service inside the DataValue
  894. // structure for the ConditionVariable Value Attribute.
  895.  
  896. assert(node.typeDefinitionObj.browseName.toString() === "ConditionVariableType");
  897. assert(node.sourceTimestamp.browseName.toString() === "SourceTimestamp");
  898. node.on("value_changed", _update_sourceTimestamp);
  899. }
  900.  
  901. /**
  902. * @method getEnabledState
  903. * @return {Boolean}
  904. */
  905. UAConditionBase.prototype.getEnabledState = function() {
  906. const conditionNode = this;
  907. return conditionNode.enabledState.getValue();
  908. };
  909. /**
  910. * @method getEnabledStateAsString
  911. * @return {String}
  912. */
  913. UAConditionBase.prototype.getEnabledStateAsString = function() {
  914. const conditionNode = this;
  915. return conditionNode.enabledState.getValueAsString();
  916. };
  917.  
  918. UAConditionBase.prototype.evaluateConditionsAfterEnabled = function() {
  919. assert(this.getEnabledState() === true);
  920. throw new Error("Unimplemented , please override");
  921. };
  922.  
  923. /**
  924. * @method _setEnabledState
  925. * @param requestedEnabledState {Boolean}
  926. * @return {StatusCode} StatusCodes.Good if successful or BadConditionAlreadyEnabled/BadConditionAlreadyDisabled
  927. * @private
  928. */
  929. UAConditionBase.prototype._setEnabledState = function(requestedEnabledState) {
  930. const conditionNode = this;
  931.  
  932. assert(_.isBoolean(requestedEnabledState));
  933.  
  934. const enabledState = conditionNode.getEnabledState();
  935. if (enabledState && requestedEnabledState) {
  936. return StatusCodes.BadConditionAlreadyEnabled;
  937. }
  938. if (!enabledState && !requestedEnabledState) {
  939. return StatusCodes.BadConditionAlreadyDisabled;
  940. }
  941.  
  942. conditionNode._branch0.setEnabledState(requestedEnabledState);
  943. //conditionNode.enabledState.setValue(requestedEnabledState);
  944.  
  945. //xx assert(conditionNode.enabledState.id.readValue().value.value === requestedEnabledState,"sanity check 1");
  946. //xx assert(conditionNode.currentBranch().getEnabledState() === requestedEnabledState,"sanity check 2");
  947.  
  948. if (!requestedEnabledState) {
  949. // as per Spec 1.0.3 part 9:
  950. //* When the Condition instance enters the Disabled state, the Retain Property of this
  951. // Condition shall be set to FALSE by the Server to indicate to the Client that the
  952. // Condition instance is currently not of interest to Clients.
  953. // TODO : shall we really set retain to false or artificially expose the retain false as false
  954. // whist enabled state is false ?
  955. conditionNode._previousRetainFlag = conditionNode.currentBranch().getRetain();
  956. conditionNode.currentBranch().setRetain(false);
  957.  
  958. // install the mechanism by which all condition values will be return
  959. // as Null | BadConditionDisabled;
  960. const statusCode = StatusCodes.BadConditionDisabled;
  961.  
  962. // a notification must be send
  963. conditionNode.raiseConditionEvent(conditionNode.currentBranch(), true);
  964. } else {
  965. //* When the Condition instance enters the enabled state, the Condition shall be
  966. // evaluated and all of its Properties updated to reflect the current values. If this
  967. // evaluation causes the Retain Property to transition to TRUE for any ConditionBranch,
  968. // then an Event Notification shall be generated for that ConditionBranch.
  969.  
  970. conditionNode.evaluateConditionsAfterEnabled();
  971.  
  972. // todo evaluate branches
  973. // conditionNode.evaluateBranches();
  974.  
  975. // restore retain flag
  976. if (conditionNode.hasOwnProperty("_previousRetainFlag")) {
  977. conditionNode.currentBranch().setRetain(conditionNode._previousRetainFlag);
  978. }
  979.  
  980. // todo send notification for branches with retain = true
  981. let nb_condition_resent = 0;
  982. if (conditionNode.currentBranch().getRetain()) {
  983. nb_condition_resent += conditionNode._resend_conditionEvents();
  984. }
  985.  
  986. if (nb_condition_resent === 0) {
  987. // a notification must be send
  988. conditionNode.raiseConditionEvent(conditionNode.currentBranch(), true);
  989. }
  990. }
  991. return StatusCodes.Good;
  992. };
  993.  
  994. /**
  995. *
  996. * @method setEnabledState
  997. * @param requestedEnabledState {Boolean}
  998. * @private
  999. */
  1000. UAConditionBase.prototype.setEnabledState = function(requestedEnabledState) {
  1001. return this._setEnabledState(requestedEnabledState);
  1002. };
  1003.  
  1004. /**
  1005. * @method setReceiveTime
  1006. * @param time {Date}
  1007. */
  1008. UAConditionBase.prototype.setReceiveTime = function(time) {
  1009. const self = this;
  1010. return self._branch0.setReceiveTime(time);
  1011. };
  1012.  
  1013. /**
  1014. * @method setLocalTime
  1015. * @param time {Date}
  1016. */
  1017. UAConditionBase.prototype.setLocalTime = function(time) {
  1018. const self = this;
  1019. return self._branch0.setLocalTime(time);
  1020. };
  1021.  
  1022. /**
  1023. * @method setTime
  1024. * @param time {Date}
  1025. */
  1026. UAConditionBase.prototype.setTime = function(time) {
  1027. const self = this;
  1028. return self._branch0.setTime(time);
  1029. };
  1030.  
  1031. UAConditionBase.prototype._assert_valid = function() {
  1032. const self = this;
  1033. assert(self.receiveTime.readValue().value.dataType === DataType.DateTime);
  1034. assert(self.receiveTime.readValue().value.value instanceof Date);
  1035.  
  1036. assert(self.localTime.readValue().value.dataType === DataType.ExtensionObject);
  1037. assert(self.message.readValue().value.dataType === DataType.LocalizedText);
  1038. assert(self.severity.readValue().value.dataType === DataType.UInt16);
  1039.  
  1040. assert(self.time.readValue().value.dataType === DataType.DateTime);
  1041. assert(self.time.readValue().value.value instanceof Date);
  1042.  
  1043. assert(self.quality.readValue().value.dataType === DataType.StatusCode);
  1044. assert(self.enabledState.readValue().value.dataType === DataType.LocalizedText);
  1045. assert(self.branchId.readValue().value.dataType === DataType.NodeId);
  1046. };
  1047.  
  1048. const browse_service = require("node-opcua-service-browse");
  1049. const BrowseDirection = require("node-opcua-data-model").BrowseDirection;
  1050.  
  1051. /**
  1052. * @method conditionOfNode
  1053. * @return {UAObject}
  1054. */
  1055. UAConditionBase.prototype.conditionOfNode = function() {
  1056. const refs = this.findReferencesExAsObject("HasCondition", BrowseDirection.Inverse);
  1057. if (refs.length === 0) {
  1058. return null;
  1059. }
  1060. assert(refs.length !== 0, "UAConditionBase must be the condition of some node");
  1061. assert(refs.length === 1, "expecting only one ConditionOf");
  1062. const node = refs[0];
  1063. assert(
  1064. node instanceof UAObject || node instanceof UAVariable,
  1065. "node for which we are the condition shall be an UAObject or UAVariable"
  1066. );
  1067. return node;
  1068. };
  1069.  
  1070. /**
  1071. * @method raiseConditionEvent
  1072. * Raise a Instance Event
  1073. * (see also UAObject#raiseEvent to raise a transient event)
  1074. * @param branch {ConditionSnapshot}
  1075. */
  1076. UAConditionBase.prototype.raiseConditionEvent = function(branch, renewEventId) {
  1077. assert(arguments.length === 2, "expecting 2 arguments");
  1078. if (renewEventId) {
  1079. branch.renewEventId();
  1080. }
  1081.  
  1082. //xx console.log("MMMMMMMM%%%%%%%%%%%%%%%%%%%%% branch " + branch.getBranchId().toString() + " eventId = " + branch.getEventId().toString("hex"));
  1083.  
  1084. assert(branch instanceof ConditionSnapshot);
  1085. const self = this;
  1086. self._assert_valid();
  1087.  
  1088. // In fact the event is raised by the object of which we are the condition
  1089. const conditionOfNode = self.conditionOfNode();
  1090.  
  1091. if (conditionOfNode) {
  1092. const eventData = branch._constructEventData();
  1093.  
  1094. self.emit("event", eventData);
  1095.  
  1096. if (conditionOfNode instanceof UAObject) {
  1097. //xx assert(conditionOfNode.eventNotifier === 0x01);
  1098. conditionOfNode._bubble_up_event(eventData);
  1099. } else {
  1100. assert(conditionOfNode instanceof UAVariable);
  1101. // in this case
  1102. const eventOfs = conditionOfNode.getEventSourceOfs();
  1103. assert(eventOfs.length === 1);
  1104. const node = eventOfs[0];
  1105. assert(node instanceof UAObject);
  1106. node._bubble_up_event(eventData);
  1107. }
  1108. }
  1109. //xx console.log("MMMMMMMM%%%%%%%%%%%%%%%%%%%%% branch " + branch.getBranchId().toString() + " eventId = " + branch.getEventId().toString("hex"));
  1110. };
  1111.  
  1112. /**
  1113. * @class ConditionInfo
  1114. * @param options {Object}
  1115. * @param options.message {String|LocalizedText} the event message
  1116. * @param options.severity {UInt16} severity
  1117. * @param options.quality {StatusCode} quality
  1118. * @param options.retain {Boolean} retain flag
  1119. * @constructor
  1120. */
  1121. function ConditionInfo(options) {
  1122. this.severity = null;
  1123. this.quality = null;
  1124. this.message = null;
  1125. this.retain = null;
  1126.  
  1127. if (options.hasOwnProperty("message") && options.message !== null) {
  1128. options.message = LocalizedText.coerce(options.message);
  1129. assert(options.message instanceof LocalizedText);
  1130. this.message = options.message;
  1131. }
  1132. if (options.hasOwnProperty("quality") && options.quality !== null) {
  1133. this.quality = options.quality;
  1134. }
  1135. if (options.hasOwnProperty("severity") && options.severity !== null) {
  1136. assert(_.isNumber(options.severity));
  1137. this.severity = options.severity;
  1138. }
  1139. if (options.hasOwnProperty("retain") && options.retain !== null) {
  1140. assert(_.isBoolean(options.retain));
  1141. this.retain = options.retain;
  1142. }
  1143. }
  1144. /**
  1145. * @method isDifferentFrom
  1146. * @param otherConditionInfo {ConditionInfo}
  1147. * @return {Boolean}
  1148. */
  1149. ConditionInfo.prototype.isDifferentFrom = function(otherConditionInfo) {
  1150. return (
  1151. this.severity !== otherConditionInfo.severity ||
  1152. this.quality !== otherConditionInfo.quality ||
  1153. this.message !== otherConditionInfo.message
  1154. );
  1155. };
  1156. exports.ConditionInfo = ConditionInfo;
  1157.  
  1158. UAConditionBase.defaultSeverity = 250;
  1159. /**
  1160. *
  1161. * @method raiseNewCondition
  1162. * @param conditionInfo {ConditionInfo}
  1163. *
  1164. */
  1165. UAConditionBase.prototype.raiseNewCondition = function(conditionInfo) {
  1166. const self = this;
  1167. if (!self.getEnabledState()) {
  1168. throw new Error("UAConditionBase#raiseNewCondition Condition is not enabled");
  1169. }
  1170.  
  1171. conditionInfo = conditionInfo || {};
  1172.  
  1173. conditionInfo.severity = conditionInfo.hasOwnProperty("severity")
  1174. ? conditionInfo.severity
  1175. : UAConditionBase.defaultSeverity;
  1176.  
  1177. //only valid for ConditionObjects
  1178. // todo check that object is of type ConditionType
  1179.  
  1180. const addressSpace = self.addressSpace;
  1181.  
  1182. const selfConditionType = self.typeDefinitionObj;
  1183. const conditionType = addressSpace.findObjectType("ConditionType");
  1184.  
  1185. assert(selfConditionType.isSupertypeOf(conditionType));
  1186.  
  1187. const branch = self.currentBranch();
  1188.  
  1189. const now = new Date();
  1190. // install the eventTimestamp
  1191. // set the received Time
  1192. branch.setTime(now);
  1193. branch.setReceiveTime(now);
  1194. branch.setLocalTime(
  1195. new TimeZone({
  1196. offset: 0,
  1197. daylightSavingInOffset: false
  1198. })
  1199. );
  1200.  
  1201. if (conditionInfo.hasOwnProperty("message") && conditionInfo.message) {
  1202. branch.setMessage(conditionInfo.message);
  1203. }
  1204. // todo receive time : when the server received the event from the underlying system.
  1205. // self.receiveTime.setValueFromSource();
  1206.  
  1207. if (conditionInfo.hasOwnProperty("severity") && conditionInfo.severity !== null) {
  1208. assert(_.isFinite(conditionInfo.severity));
  1209. branch.setSeverity(conditionInfo.severity);
  1210. }
  1211. if (conditionInfo.hasOwnProperty("quality") && conditionInfo.quality !== null) {
  1212. assert(conditionInfo.quality instanceof StatusCode);
  1213. branch.setQuality(conditionInfo.quality);
  1214. }
  1215. if (conditionInfo.hasOwnProperty("retain") && conditionInfo.retain !== null) {
  1216. assert(_.isBoolean(conditionInfo.retain));
  1217. branch.setRetain(!!conditionInfo.retain);
  1218. }
  1219.  
  1220. self.raiseConditionEvent(branch, true);
  1221. };
  1222.  
  1223. UAConditionBase.prototype.raiseNewBranchState = function(branch) {
  1224. const self = this;
  1225. self.raiseConditionEvent(branch, true);
  1226.  
  1227. if (branch.getBranchId() !== NodeId.NullNodeId && !branch.getRetain()) {
  1228. //xx console.log(" Deleting not longer needed branch ", branch.getBranchId().toString());
  1229. // branch can be deleted
  1230. self.deleteBranch(branch);
  1231. }
  1232. };
  1233.  
  1234. function sameBuffer(b1, b2) {
  1235. if (!b1 && !b2) {
  1236. return true;
  1237. }
  1238. if (b1 && !b2) {
  1239. return false;
  1240. }
  1241. if (!b1 && b2) {
  1242. return false;
  1243. }
  1244. assert(b1 instanceof Buffer);
  1245. assert(b2 instanceof Buffer);
  1246. if (b1.length !== b2.length) {
  1247. return false;
  1248. }
  1249. /*
  1250. var bb1 = (Buffer.from(b1)).toString("hex");
  1251. var bb2 = (Buffer.from(b2)).toString("hex");
  1252. return bb1 === bb2;
  1253. */
  1254. const n = b1.length;
  1255. for (let i = 0; i < n; i++) {
  1256. if (b1[i] !== b2[i]) {
  1257. return false;
  1258. }
  1259. }
  1260. return true;
  1261. }
  1262. UAConditionBase.prototype._findBranchForEventId = function(eventId) {
  1263. const conditionNode = this;
  1264. if (sameBuffer(conditionNode.eventId.readValue().value.value, eventId)) {
  1265. return conditionNode.currentBranch();
  1266. }
  1267. const e = _.filter(conditionNode._branches, function(branch, key) {
  1268. return sameBuffer(branch.getEventId(), eventId);
  1269. });
  1270. if (e.length === 1) {
  1271. return e[0];
  1272. }
  1273. assert(e.length === 0, "cannot have 2 branches with same eventId");
  1274. return null; // not found
  1275. };
  1276.  
  1277. exports.UAConditionBase = UAConditionBase;
  1278.  
  1279. /**
  1280. * @method _raiseAuditConditionCommentEvent
  1281. * @param sourceName {string}
  1282. * @param eventId {Buffer}
  1283. * @param comment {LocalizedText}
  1284. * @private
  1285. */
  1286. UAConditionBase.prototype._raiseAuditConditionCommentEvent = function(sourceName, eventId, comment) {
  1287. assert(eventId === null || eventId instanceof Buffer);
  1288. assert(comment instanceof LocalizedText);
  1289. const server = this.addressSpace.rootFolder.objects.server;
  1290.  
  1291. const now = new Date();
  1292.  
  1293. //xx if (true || server.isAuditing) {
  1294. // ----------------------------------------------------------------------------------------------------------------
  1295. server.raiseEvent("AuditConditionCommentEventType", {
  1296. // AuditEventType
  1297. /* part 5 - 6.4.3 AuditEventType */
  1298. actionTimeStamp: {
  1299. dataType: "DateTime",
  1300. value: now
  1301. },
  1302. status: {
  1303. dataType: "Boolean",
  1304. value: true
  1305. },
  1306.  
  1307. serverId: {
  1308. dataType: "String",
  1309. value: ""
  1310. },
  1311.  
  1312. // ClientAuditEntryId contains the human-readable AuditEntryId defined in Part 3.
  1313. clientAuditEntryId: {
  1314. dataType: "String",
  1315. value: ""
  1316. },
  1317.  
  1318. // The ClientUserId identifies the user of the client requesting an action. The ClientUserId can be
  1319. // obtained from the UserIdentityToken passed in the ActivateSession call.
  1320. clientUserId: {
  1321. dataType: "String",
  1322. value: ""
  1323. },
  1324. sourceName: {
  1325. dataType: "String",
  1326. value: sourceName
  1327. },
  1328.  
  1329. // AuditUpdateMethodEventType
  1330. methodId: {},
  1331. inputArguments: {},
  1332. // AuditConditionCommentEventType
  1333. eventId: {
  1334. dataType: DataType.ByteString,
  1335. value: eventId
  1336. },
  1337. comment: {
  1338. dataType: DataType.LocalizedText,
  1339. value: comment
  1340. }
  1341. });
  1342. //xx }
  1343. };
  1344.  
  1345. /**
  1346. * @method currentBranch
  1347. * @return {ConditionSnapshot}
  1348. */
  1349. UAConditionBase.prototype.currentBranch = function() {
  1350. return this._branch0;
  1351. };
  1352.  
  1353. /**
  1354. *
  1355. * Helper method to handle condition methods that takes a branchId and a comment
  1356. * @method with_condition_method$
  1357. * @param inputArguments {Array<Variant>}
  1358. * @param context {Object}
  1359. * @param context.object {BaseNode}
  1360. * @param callback {Function}
  1361. * @param callback.err {Error|null}
  1362. * @param callback.result {Object}
  1363. * @param callback.result.statusCode {StatusCode}
  1364. * @param inner_func {Function}
  1365. * @param inner_func.eventId {Buffer|null}
  1366. * @param inner_func.comment {LocalizedText}
  1367. * @param inner_func.branch {ConditionSnapshot}
  1368. * @param inner_func.conditionNode {UAConditionBase}
  1369. *
  1370. * @return {void}
  1371. */
  1372. UAConditionBase.with_condition_method = function(inputArguments, context, callback, inner_func) {
  1373. const conditionNode = context.object;
  1374.  
  1375. //xx console.log(inputArguments.map(function(a){return a.toString()}));
  1376. if (!(conditionNode instanceof UAConditionBase)) {
  1377. callback(null, {
  1378. statusCode: StatusCodes.BadNodeIdInvalid
  1379. });
  1380. return;
  1381. }
  1382.  
  1383. if (!conditionNode.getEnabledState()) {
  1384. callback(null, {
  1385. statusCode: StatusCodes.BadConditionDisabled
  1386. });
  1387. return;
  1388. }
  1389.  
  1390. // inputArguments has 2 arguments
  1391. // EventId => ByteString The Identifier of the event to comment
  1392. // Comment => LocalizedText The Comment to add to the condition
  1393. assert(inputArguments.length === 2);
  1394. assert(inputArguments[0].dataType === DataType.ByteString);
  1395. assert(inputArguments[1].dataType === DataType.LocalizedText);
  1396.  
  1397. const eventId = inputArguments[0].value;
  1398. assert(!eventId || eventId instanceof Buffer);
  1399.  
  1400. const comment = inputArguments[1].value;
  1401. assert(comment instanceof LocalizedText);
  1402.  
  1403. const branch = conditionNode._findBranchForEventId(eventId);
  1404. if (!branch) {
  1405. callback(null, {
  1406. statusCode: StatusCodes.BadEventIdUnknown
  1407. });
  1408. return;
  1409. }
  1410. assert(branch instanceof ConditionSnapshot);
  1411.  
  1412. const statusCode = inner_func(eventId, comment, branch, conditionNode);
  1413.  
  1414. // record also who did the call
  1415. branch.setClientUserId(context.userIdentity || "<unknown client user id>");
  1416.  
  1417. callback(null, {
  1418. statusCode: statusCode
  1419. });
  1420. };
  1421.  
  1422. UAConditionBase.prototype._resend_conditionEvents = function() {
  1423. // for the time being , only current branch
  1424. const self = this;
  1425. const currentBranch = self.currentBranch();
  1426. if (currentBranch.getRetain()) {
  1427. debugLog(" resending condition event for " + self.browseName.toString());
  1428. self.raiseConditionEvent(currentBranch, false);
  1429. return 1;
  1430. }
  1431. return 0;
  1432. };
  1433.  
  1434. BaseNode.prototype._conditionRefresh = function(_cache) {
  1435. // visit all notifiers recursively
  1436. _cache = _cache || {};
  1437. const self = this;
  1438. const notifiers = self.getNotifiers();
  1439. const eventSources = self.getEventSources();
  1440.  
  1441. const conditions = this.findReferencesAsObject("HasCondition", true);
  1442. let i;
  1443.  
  1444. for (i = 0; i < conditions.length; i++) {
  1445. const condition = conditions[i];
  1446. if (condition instanceof UAConditionBase) {
  1447. condition._resend_conditionEvents();
  1448. }
  1449. }
  1450. const arr = [].concat(notifiers, eventSources);
  1451.  
  1452. for (i = 0; i < arr.length; i++) {
  1453. const notifier = arr[i];
  1454. const key = notifier.nodeId.toString();
  1455. if (!_cache[key]) {
  1456. _cache[key] = notifier;
  1457. if (notifier._conditionRefresh) {
  1458. notifier._conditionRefresh(_cache);
  1459. }
  1460. }
  1461. }
  1462. };
  1463.  
  1464. function _perform_condition_refresh(addressSpace, inputArguments, context) {
  1465. // --- possible StatusCodes:
  1466. //
  1467. // Bad_SubscriptionIdInvalid See Part 4 for the description of this result code
  1468. // Bad_RefreshInProgress See Table 74 for the description of this result code
  1469. // Bad_UserAccessDenied The Method was not called in the context of the Session
  1470. // that owns the Subscription
  1471. //
  1472.  
  1473. // istanbul ignore next
  1474. if (addressSpace._condition_refresh_in_progress) {
  1475. // a refresh operation is already in progress....
  1476. return StatusCodes.BadRefreshInProgress;
  1477. }
  1478.  
  1479. addressSpace._condition_refresh_in_progress = true;
  1480.  
  1481. const server = context.object.addressSpace.rootFolder.objects.server;
  1482. assert(server instanceof UAObject);
  1483.  
  1484. const refreshStartEventType = addressSpace.findEventType("RefreshStartEventType");
  1485. const refreshEndEventType = addressSpace.findEventType("RefreshEndEventType");
  1486.  
  1487. assert(refreshStartEventType instanceof UAObjectType);
  1488. assert(refreshEndEventType instanceof UAObjectType);
  1489.  
  1490. server.raiseEvent(refreshStartEventType, {});
  1491. // todo : resend retained conditions
  1492.  
  1493. // starting from server object ..
  1494. // evaluated all --> hasNotifier/hasEventSource -> node
  1495. server._conditionRefresh();
  1496.  
  1497. server.raiseEvent(refreshEndEventType, {});
  1498.  
  1499. addressSpace._condition_refresh_in_progress = false;
  1500.  
  1501. return StatusCodes.Good;
  1502. }
  1503.  
  1504. function _add_comment_method(inputArguments, context, callback) {
  1505. //
  1506. // The AddComment Method is used to apply a comment to a specific state of a Condition
  1507. // instance. Normally, the NodeId of the object instance as the ObjectId is passed to the Call
  1508. // Service. However, some Servers do not expose Condition instances in the AddressSpace.
  1509. // Therefore all Servers shall also allow Clients to call the AddComment Method by specifying
  1510. // ConditionId as the ObjectId. The Method cannot be called with an ObjectId of the
  1511. // ConditionType Node.
  1512. // Signature
  1513. // - EventId EventId identifying a particular Event Notification where a state was reported for a
  1514. // Condition.
  1515. // - Comment A localized text to be applied to the Condition.
  1516. //
  1517. // AlwaysGeneratesEvent AuditConditionCommentEventType
  1518. //
  1519. UAConditionBase.with_condition_method(inputArguments, context, callback, function(
  1520. eventId,
  1521. comment,
  1522. branch,
  1523. conditionNode
  1524. ) {
  1525. assert(inputArguments instanceof Array);
  1526. assert(eventId instanceof Buffer || eventId === null);
  1527. assert(branch instanceof ConditionSnapshot);
  1528. branch.setComment(comment);
  1529.  
  1530. const sourceName = "Method/AddComment";
  1531.  
  1532. conditionNode._raiseAuditConditionCommentEvent(sourceName, eventId, comment);
  1533.  
  1534. // raise new event
  1535. conditionNode.raiseConditionEvent(branch, true);
  1536.  
  1537. /**
  1538. * raised when the branch has been added a comment
  1539. * @event addComment
  1540. * @param eventId {Buffer|null}
  1541. * @param comment {LocalizedText}
  1542. * @param branch {ConditionSnapshot}
  1543. */
  1544. conditionNode.emit("addComment", eventId, comment, branch);
  1545.  
  1546. return StatusCodes.Good;
  1547. });
  1548. }
  1549.  
  1550. function _enable_method(inputArguments, context, callback) {
  1551. assert(inputArguments.length === 0);
  1552. const conditionNode = context.object;
  1553. assert(conditionNode);
  1554.  
  1555. if (!(conditionNode instanceof UAConditionBase)) {
  1556. return callback(null, {
  1557. statusCode: StatusCodes.BadNodeIdInvalid
  1558. });
  1559. }
  1560. const statusCode = conditionNode._setEnabledState(true);
  1561. return callback(null, {
  1562. statusCode: statusCode
  1563. });
  1564. }
  1565.  
  1566. function _disable_method(inputArguments, context, callback) {
  1567. assert(inputArguments.length === 0);
  1568.  
  1569. const conditionNode = context.object;
  1570. assert(conditionNode);
  1571.  
  1572. if (!(conditionNode instanceof UAConditionBase)) {
  1573. return callback(null, {
  1574. statusCode: StatusCodes.BadNodeIdInvalid
  1575. });
  1576. }
  1577. const statusCode = conditionNode._setEnabledState(false);
  1578. return callback(null, {
  1579. statusCode: statusCode
  1580. });
  1581. }
  1582.  
  1583. /**
  1584. * verify that the subscription id belongs to the session that make the call.
  1585. * @method _check_subscription_id_is_valid
  1586. * @param subscriptionId {Number}
  1587. * @param context {Object}
  1588. * @private
  1589. */
  1590. function _check_subscription_id_is_valid(subscriptionId, context) {
  1591. /// todo: return StatusCodes.BadSubscriptionIdInvalid; if subscriptionId doesn't belong to session...
  1592. return StatusCodes.Good;
  1593. }
  1594.  
  1595. function _condition_refresh_method(inputArguments, context, callback) {
  1596. // arguments : IntegerId SubscriptionId
  1597. assert(inputArguments.length === 1);
  1598.  
  1599. const addressSpace = context.object.addressSpace;
  1600. if (doDebug) {
  1601. debugLog(" ConditionType.ConditionRefresh ! subscriptionId =".red.bgWhite, inputArguments[0].toString());
  1602. }
  1603. const subscriptionId = inputArguments[0].value;
  1604.  
  1605. let statusCode = _check_subscription_id_is_valid(subscriptionId, context);
  1606. if (statusCode !== StatusCodes.Good) {
  1607. return statusCode;
  1608. }
  1609.  
  1610. statusCode = _perform_condition_refresh(addressSpace, inputArguments, context);
  1611. return callback(null, {
  1612. statusCode: statusCode
  1613. });
  1614. }
  1615.  
  1616. function _condition_refresh2_method(inputArguments, context, callback) {
  1617. // arguments : IntegerId SubscriptionId
  1618. // arguments : IntegerId MonitoredItemId
  1619. assert(inputArguments.length === 2);
  1620.  
  1621. const addressSpace = context.object.addressSpace;
  1622.  
  1623. // istanbul ignore next
  1624. if (doDebug) {
  1625. debugLog(" ConditionType.conditionRefresh2 !".cyan.bgWhite);
  1626. }
  1627.  
  1628. //xx var subscriptionId = inputArguments[0].value;
  1629. //xx var monitoredItemId = inputArguments[1].value;
  1630.  
  1631. const statusCode = _perform_condition_refresh(addressSpace, inputArguments, context);
  1632. return callback(null, {
  1633. statusCode: statusCode
  1634. });
  1635. }
  1636.  
  1637. UAConditionBase.install_condition_refresh_handle = function _install_condition_refresh_handle(addressSpace) {
  1638. //
  1639. // install CondititionRefresh
  1640. //
  1641. // NOTE:
  1642. // OPCUA doesn't implement the condition refresh method ! yet
  1643. // .5.7 ConditionRefresh Method
  1644. // ConditionRefresh allows a Client to request a Refresh of all Condition instances that currently
  1645. // are in an interesting state (they have the Retain flag set). This includes previous states of a
  1646. // Condition instance for which the Server maintains Branches. A Client would typically invoke
  1647. // this Method when it initially connects to a Server and following any situations, such as
  1648. // communication disruptions, in which it would require resynchronization with the Server. This
  1649. // Method is only available on the ConditionType or its subtypes. To invoke this Method, the call
  1650. // shall pass the well known MethodId of the Method on the ConditionType and the ObjectId
  1651. // shall be the well known ObjectId of the ConditionType Object.
  1652.  
  1653. const conditionType = addressSpace.findEventType("ConditionType");
  1654. assert(conditionType !== null);
  1655.  
  1656. conditionType.disable.bindMethod(_disable_method);
  1657. conditionType.enable.bindMethod(_enable_method);
  1658.  
  1659. conditionType.conditionRefresh.bindMethod(_condition_refresh_method);
  1660.  
  1661. conditionType.conditionRefresh2.bindMethod(_condition_refresh2_method);
  1662.  
  1663. // those methods can be call on the ConditionType or on the ConditionInstance itself...
  1664. conditionType.addComment.bindMethod(_add_comment_method);
  1665. };
  1666.  
  1667. /**
  1668. * @method _getCompositeKey
  1669. * @param node {BaseNode}
  1670. * @param key {String}
  1671. * @return {BaseNode}
  1672. * @private
  1673. *
  1674. * @example
  1675. *
  1676. * var node = _getComposite(node,"enabledState.id");
  1677. *
  1678. */
  1679. function _getCompositeKey(node, key) {
  1680. let cur = node;
  1681. const elements = key.split(".");
  1682. for (let i = 0; i < elements.length; i++) {
  1683. const e = elements[i];
  1684.  
  1685. // istanbul ignore next
  1686. if (!cur.hasOwnProperty(e)) {
  1687. throw new Error(" cannot extract '" + key + "' from " + node.browseName.toString());
  1688. }
  1689.  
  1690. cur = cur[e];
  1691. }
  1692. return cur;
  1693. }
  1694.  
  1695. /**
  1696. * instantiate a Condition.
  1697. * this will create the unique EventId and will set eventType
  1698. * @method instantiate
  1699. * @param namespace {Namespace}
  1700. * @param conditionTypeId {String|NodeId} the EventType to instantiate
  1701. * @param options {object}
  1702. * @param options.browseName {String|QualifiedName}
  1703. * @param options.componentOf {NodeId|UAObject}
  1704. * @param options.conditionOf {NodeId|UAObject} Mandatory
  1705. * @param options.organizedBy {NodeId|UAObject} ( only provide componentOf or organizedBy but not both)
  1706. * @param [options.conditionClass =BaseConditionClassType] {NodeId|UAObject}
  1707. * The condition Class nodeId or object used to set the ConditionClassId and
  1708. * ConditionClassName properties of the condition.
  1709. *
  1710. * @param options.conditionSource {NodeId|UAObject} the condition source node.
  1711. * this node must be marked a EventSource.
  1712. * the conditionSource is used to populate the sourceNode and
  1713. * sourceName variables defined by BaseEventType
  1714. * @param options.conditionName {String} the condition Name
  1715. * @param [options.optionals] [Array<String>] an Array of optinals fields
  1716. *
  1717. * @param data {object} a object containing the value to set
  1718. * @param data.eventId {String|NodeId} the EventType Identifier to instantiate (type cannot be abstract)
  1719.  
  1720. * @return node {UAConditionBase}
  1721. */
  1722. UAConditionBase.instantiate = function(namespace, conditionTypeId, options, data) {
  1723. /* eslint max-statements: ["error", 100] */
  1724. assert(namespace instanceof Namespace);
  1725. const addressSpace = namespace.addressSpace;
  1726.  
  1727. const conditionType = addressSpace.findEventType(conditionTypeId);
  1728.  
  1729. /* istanbul ignore next */
  1730. if (!conditionType) {
  1731. throw new Error(" cannot find Condition Type for " + conditionTypeId);
  1732. }
  1733.  
  1734. // reminder : abstract event type cannot be instantiated directly !
  1735. assert(!conditionType.isAbstract);
  1736.  
  1737. const baseConditionEventType = addressSpace.findEventType("ConditionType");
  1738. /* istanbul ignore next */
  1739. if (!baseConditionEventType) {
  1740. throw new Error("cannot find ConditionType");
  1741. }
  1742.  
  1743. assert(conditionType.isSupertypeOf(baseConditionEventType));
  1744.  
  1745. // assert(_.isString(options.browseName));
  1746. options.browseName = options.browseName || "??? instantiateCondition - missing browseName";
  1747.  
  1748. options.optionals = options.optionals || [];
  1749.  
  1750. // now optionals in 1.04
  1751. options.optionals.push("EventType");
  1752. options.optionals.push("BranchId");
  1753.  
  1754. //
  1755. options.optionals.push("Comment");
  1756. options.optionals.push("Comment.SourceTimestamp");
  1757. options.optionals.push("EnabledState.TrueState");
  1758. options.optionals.push("EnabledState.TrueState");
  1759. options.optionals.push("EnabledState.FalseState");
  1760.  
  1761. options.optionals.push("EnabledState.TransitionTime");
  1762. options.optionals.push("EnabledState.EffectiveTransitionTime");
  1763. options.optionals.push("EnabledState.EffectiveDisplayName");
  1764.  
  1765. const conditionNode = conditionType.instantiate(options);
  1766. Object.setPrototypeOf(conditionNode, UAConditionBase.prototype);
  1767. conditionNode.initialize();
  1768.  
  1769. assert(
  1770. options.hasOwnProperty("conditionSource"),
  1771. "must specify a condition source either as null or as a UAObject"
  1772. );
  1773. if (!options.conditionOf) {
  1774. options.conditionOf = options.conditionSource;
  1775. }
  1776. if (options.conditionOf) {
  1777. assert(options.hasOwnProperty("conditionOf")); // must provide a conditionOf
  1778. options.conditionOf = addressSpace._coerceNode(options.conditionOf);
  1779.  
  1780. // HasCondition References can be used in the Type definition of an Object or a Variable.
  1781. assert(options.conditionOf instanceof UAObject || options.conditionOf instanceof UAVariable);
  1782.  
  1783. conditionNode.addReference({
  1784. referenceType: "HasCondition",
  1785. isForward: false,
  1786. nodeId: options.conditionOf
  1787. });
  1788. assert(conditionNode.conditionOfNode().nodeId === options.conditionOf.nodeId);
  1789. }
  1790.  
  1791. /**
  1792. * dataType is DataType.NodeId
  1793. * @property eventType
  1794. * @type {UAVariableType}
  1795. *
  1796. */
  1797. // the constant property of this condition
  1798. conditionNode.eventType.setValueFromSource({
  1799. dataType: DataType.NodeId,
  1800. value: conditionType.nodeId
  1801. });
  1802.  
  1803. data = data || {};
  1804. // install initial branch ID (null NodeId);
  1805. /**
  1806. * dataType is DataType.NodeId
  1807. * @property branchId
  1808. * @type {UAVariableType}
  1809. *
  1810. */
  1811. conditionNode.branchId.setValueFromSource({
  1812. dataType: DataType.NodeId,
  1813. value: NodeId.NullNodeId
  1814. });
  1815.  
  1816. // install 'Comment' condition variable
  1817. /**
  1818. * dataType is DataType.LocalizedText
  1819. * @property comment
  1820. * @type {UAVariableType}
  1821. *
  1822. */
  1823. _install_condition_variable_type(conditionNode.comment);
  1824.  
  1825. // install 'Quality' condition variable
  1826. /**
  1827. * dataType is DataType.StatusCode
  1828. * @property quality
  1829. * @type {UAVariableType}
  1830. *
  1831. */
  1832. _install_condition_variable_type(conditionNode.quality);
  1833. //xx conditionNode.quality.setValueFromSource({dataType: DataType.StatusCode,value: StatusCodes.Good });
  1834.  
  1835. // install 'LastSeverity' condition variable
  1836. /**
  1837. * dataType is DataType.StatusCode
  1838. * @property lastSeverity
  1839. * @type {UAVariableType}
  1840. *
  1841. */
  1842. _install_condition_variable_type(conditionNode.lastSeverity);
  1843. //xx conditionNode.severity.setValueFromSource({dataType: DataType.UInt16,value: 0 });
  1844. //xx conditionNode.lastSeverity.setValueFromSource({dataType: DataType.UInt16,value: 0 });
  1845.  
  1846. // install 'EnabledState' TwoStateVariable
  1847. /**
  1848. * @property enabledState
  1849. * @type {UATwoStateVariable}
  1850. */
  1851. // -------------- fixing missing EnabledState.EffectiveDisplayName
  1852. if (!conditionNode.enabledState.effectiveDisplayName) {
  1853. namespace.addVariable({
  1854. browseName: "EffectiveDisplayName",
  1855. dataType: "LocalizedText",
  1856. propertyOf: conditionNode.enabledState
  1857. });
  1858. }
  1859. AddressSpace._install_TwoStateVariable_machinery(conditionNode.enabledState, {
  1860. trueState: "Enabled",
  1861. falseState: "Disabled"
  1862. });
  1863. assert(conditionNode.enabledState._trueState === "Enabled");
  1864. assert(conditionNode.enabledState._falseState === "Disabled");
  1865.  
  1866. // installing sourceName and sourceNode
  1867. conditionNode.enabledState.setValue(true);
  1868.  
  1869. // set properties to in initial values
  1870. Object.keys(data).forEach(function(key) {
  1871. const varNode = _getCompositeKey(conditionNode, key);
  1872. assert(varNode instanceof UAVariable);
  1873.  
  1874. const variant = new Variant(data[key]);
  1875.  
  1876. // check that Variant DataType is compatible with the UAVariable dataType
  1877. //xx var nodeDataType = addressSpace.findNode(varNode.dataType).browseName;
  1878.  
  1879. /* istanbul ignore next */
  1880. if (!varNode._validate_DataType(variant.dataType)) {
  1881. throw new Error(" Invalid variant dataType " + variant + " " + varNode.browseName.toString());
  1882. }
  1883.  
  1884. const value = new Variant(data[key]);
  1885.  
  1886. varNode.setValueFromSource(value);
  1887. });
  1888.  
  1889. // bind condition methods -
  1890. /**
  1891. * @property enable
  1892. * @type {UAMethod}
  1893. */
  1894. conditionNode.enable.bindMethod(_enable_method);
  1895.  
  1896. /**
  1897. * @property disable
  1898. * @type {UAMethod}
  1899. */
  1900. conditionNode.disable.bindMethod(_disable_method);
  1901.  
  1902. // bind condition methods - AddComment
  1903. /**
  1904. * @property addComment
  1905. * @type {UAMethod}
  1906. */
  1907. conditionNode.addComment.bindMethod(_add_comment_method);
  1908.  
  1909. assert(conditionNode instanceof UAConditionBase);
  1910.  
  1911. // ConditionSource => cf SourceNode
  1912. // As per spec OPCUA 1.03 part 9 page 54:
  1913. // The ConditionType inherits all Properties of the BaseEventType. Their semantic is defined in
  1914. // Part 5. SourceNode identifies the ConditionSource.
  1915. // The SourceNode is the Node which the condition is associated with, it may be the same as the
  1916. // InputNode for an alarm, but it may be a separate node. For example a motor, which is a
  1917. // variable with a value that is an RPM, may be the ConditionSource for Conditions that are
  1918. // related to the motor as well as a temperature sensor associated with the motor. In the former
  1919. // the InputNode for the High RPM alarm is the value of the Motor RPM, while in the later the
  1920. // InputNode of the High Alarm would be the value of the temperature sensor that is associated
  1921. // with the motor.
  1922. /**
  1923. * dataType is DataType.NodeId
  1924. * @property sourceNode
  1925. * @type {UAVariableType}
  1926. *
  1927. */
  1928. if (options.conditionSource !== null) {
  1929. options.conditionSource = addressSpace._coerceNode(options.conditionSource);
  1930. assert(options.conditionSource instanceof BaseNode);
  1931.  
  1932. const conditionSourceNode = addressSpace.findNode(options.conditionSource.nodeId);
  1933.  
  1934. conditionNode.sourceNode.setValueFromSource({
  1935. dataType: DataType.NodeId,
  1936. value: conditionSourceNode.nodeId
  1937. });
  1938.  
  1939. // conditionSourceNode node must be registered as a EventSource of an other node.
  1940. // As per spec OPCUA 1.03 part 9 page 54:
  1941. // HasNotifier and HasEventSource References are used to expose the hierarchical organization
  1942. // of Event notifying Objects and ConditionSources. An Event notifying Object represents
  1943. // typically an area of Operator responsibility. The definition of such an area configuration is
  1944. // outside the scope of this standard. If areas are available they shall be linked together and
  1945. // with the included ConditionSources using the HasNotifier and the HasEventSource Reference
  1946. // Types. The Server Object shall be the root of this hierarchy.
  1947. assert(conditionSourceNode.getEventSourceOfs().length >= 1, "conditionSourceNode must be an event source");
  1948.  
  1949. const context = SessionContext.defaultContext;
  1950. // set source Node (defined in UABaseEventType)
  1951. conditionNode.sourceNode.setValueFromSource(
  1952. conditionSourceNode.readAttribute(context, AttributeIds.NodeId).value
  1953. );
  1954.  
  1955. // set source Name (defined in UABaseEventType)
  1956. conditionNode.sourceName.setValueFromSource(
  1957. conditionSourceNode.readAttribute(context, AttributeIds.DisplayName).value
  1958. );
  1959. }
  1960.  
  1961. conditionNode.eventType.setValueFromSource({
  1962. dataType: DataType.NodeId,
  1963. value: conditionType.nodeId
  1964. });
  1965. // as per spec:
  1966.  
  1967. /**
  1968. *
  1969. * dataType: DataType.NodeId
  1970. *
  1971. * As per spec OPCUA 1.03 part 9:
  1972. * ConditionClassId specifies in which domain this Condition is used. It is the NodeId of the
  1973. * corresponding ConditionClassType. See 5.9 for the definition of ConditionClass and a set of
  1974. * ConditionClasses defined in this standard. When using this Property for filtering, Clients have
  1975. * to specify all individual ConditionClassType NodeIds. The OfType operator cannot be applied.
  1976. * BaseConditionClassType is used as class whenever a Condition cannot be assigned to a
  1977. * more concrete class.
  1978. *
  1979. * BaseConditionClassType
  1980. * |
  1981. * +---------------------------+----------------------------+
  1982. * | | |
  1983. * ProcessConditionClassType MaintenanceConditionClassType SystemConditionClassType
  1984. *
  1985. * @property conditionName
  1986. * @type {UAVariable}
  1987. */
  1988. const baseConditionClassType = addressSpace.findObjectType("ProcessConditionClassType");
  1989. //assert(baseConditionClassType,"Expecting BaseConditionClassType to be in addressSpace");
  1990. let conditionClassId = baseConditionClassType ? baseConditionClassType.nodeId : NodeId.NullNodeId;
  1991. let conditionClassName = baseConditionClassType ? baseConditionClassType.displayName[0] : "";
  1992. if (options.conditionClass) {
  1993. if (_.isString(options.conditionClass)) {
  1994. options.conditionClass = addressSpace.findObjectType(options.conditionClass);
  1995. }
  1996. const conditionClassNode = addressSpace._coerceNode(options.conditionClass);
  1997. if (!conditionClassNode) {
  1998. throw new Error("cannot find condition class " + options.conditionClass.toString());
  1999. }
  2000. conditionClassId = conditionClassNode.nodeId;
  2001. conditionClassName = conditionClassNode.displayName[0];
  2002. }
  2003. conditionNode.conditionClassId.setValueFromSource({
  2004. dataType: DataType.NodeId,
  2005. value: conditionClassId
  2006. });
  2007.  
  2008. // as per spec:
  2009. // ConditionClassName provides the display name of the ConditionClassType.
  2010. conditionNode.conditionClassName.setValueFromSource({
  2011. dataType: DataType.LocalizedText,
  2012. value: coerceLocalizedText(conditionClassName)
  2013. });
  2014.  
  2015. // as per spec:
  2016. /**
  2017. *
  2018. * dataType: DataType.String
  2019. *
  2020. * As per spec OPCUA 1.03 part 9:
  2021. * ConditionName identifies the Condition instance that the Event originated from. It can be used
  2022. * together with the SourceName in a user display to distinguish between different Condition
  2023. * instances. If a ConditionSource has only one instance of a ConditionType, and the Server has
  2024. * no instance name, the Server shall supply the ConditionType browse name.
  2025. * @property conditionName
  2026. * @type {UAVariable}
  2027. */
  2028. const conditionName = options.conditionName || "Unset Condition Name";
  2029. assert(_.isString(conditionName));
  2030. conditionNode.conditionName.setValueFromSource({
  2031. dataType: DataType.String,
  2032. value: conditionName
  2033. });
  2034.  
  2035. // set SourceNode and SourceName based on HasCondition node
  2036. const sourceNodes = conditionNode.findReferencesAsObject("HasCondition", false);
  2037. if (sourceNodes.length) {
  2038. assert(sourceNodes.length === 1);
  2039. conditionNode.setSourceNode(sourceNodes[0].nodeId);
  2040. conditionNode.setSourceName(sourceNodes[0].browseName.toString());
  2041. }
  2042.  
  2043. conditionNode.post_initialize();
  2044.  
  2045. const branch0 = conditionNode.currentBranch();
  2046. branch0.setRetain(false);
  2047. branch0.setComment("Initialized");
  2048. branch0.setQuality(StatusCodes.Good);
  2049. branch0.setSeverity(0);
  2050. branch0.setLocalTime(
  2051. new TimeZone({
  2052. offset: 0,
  2053. daylightSavingInOffset: false
  2054. })
  2055. );
  2056. branch0.setMessage(" ");
  2057.  
  2058. branch0.setReceiveTime(minDate);
  2059. branch0.setTime(minDate);
  2060.  
  2061. // UAConditionBase
  2062. return conditionNode;
  2063. };
  2064.  
  2065. /*
  2066. As per spec OPCUA 1.03 part 9:
  2067.  
  2068. A Condition’s EnabledState effects the generation of Event Notifications and as such results
  2069. in the following specific behaviour:
  2070. * When the Condition instance enters the Disabled state, the Retain Property of this
  2071. Condition shall be set to FALSE by the Server to indicate to the Client that the
  2072. Condition instance is currently not of interest to Clients.
  2073. * When the Condition instance enters the enabled state, the Condition shall be
  2074. evaluated and all of its Properties updated to reflect the current values. If this
  2075. evaluation causes the Retain Property to transition to TRUE for any ConditionBranch,
  2076. then an Event Notification shall be generated for that ConditionBranch.
  2077. * The Server may choose to continue to test for a Condition instance while it is
  2078. Disabled. However, no Event Notifications will be generated while the Condition
  2079. instance is disabled.
  2080. * For any Condition that exists in the AddressSpace the Attributes and the following
  2081. Variables will continue to have valid values even in the Disabled state; EventId, Event
  2082. Type, Source Node, Source Name, Time, and EnabledState.
  2083. Other properties may no longer provide current valid values.
  2084. All Variables that are no longer provided shall return a status of Bad_ConditionDisabled.
  2085. The Event that reports the Disabled state should report the properties as NULL or with a status
  2086. of Bad_ConditionDisabled.
  2087. When enabled, changes to the following components shall cause a ConditionType Event Notification:
  2088. - Quality
  2089. - Severity (inherited from BaseEventType)
  2090. - Comment
  2091.  
  2092. // spec :
  2093. // The HasCondition ReferenceType is a concrete ReferenceType and can be used directly. It is
  2094. // a subtype of NonHierarchicalReferences.
  2095. // The semantic of this ReferenceType is to specify the relationship between a ConditionSource
  2096. // and its Conditions. Each ConditionSource shall be the target of a HasEventSource Reference
  2097. // or a sub type of HasEventSource. The AddressSpace organisation that shall be provided for
  2098. // Clients to detect Conditions and ConditionSources is defined in Clause 6. Various examples
  2099. // for the use of this ReferenceType can be found in B.2.
  2100. // HasCondition References can be used in the Type definition of an Object or a Variable. In this
  2101. // case, the SourceNode of this ReferenceType shall be an ObjectType or VariableType Node or
  2102. // one of their InstanceDeclaration Nodes. The TargetNode shall be a Condition instance
  2103. // declaration or a ConditionType. The following rules for instantiation apply:
  2104. //  All HasCondition References used in a Type shall exist in instances of these Types as
  2105. // well.
  2106. //  If the TargetNode in the Type definition is a ConditionType, the same TargetNode will
  2107. // be referenced on the instance.
  2108. // HasCondition References may be used solely in the instance space when they are not
  2109. // available in Type definitions. In this case the SourceNode of this ReferenceType shall be an
  2110. // Object, Variable or Method Node. The TargetNode shall be a Condition instance or a
  2111. // ConditionType.
  2112.  
  2113. */
  2114.