- "use strict";
- /**
- * @module opcua.address_space.AlarmsAndConditions
- */
-
- require("set-prototype-of");
- const EventEmitter = require("events").EventEmitter;
- const util = require("util");
- const assert = require("node-opcua-assert").assert;
- const _ = require("underscore");
-
- const UAVariable = require("../ua_variable").UAVariable;
- const Variant = require("node-opcua-variant").Variant;
- const DataType = require("node-opcua-variant").DataType;
- const StatusCodes = require("node-opcua-status-code").StatusCodes;
- const StatusCode = require("node-opcua-status-code").StatusCode;
- const UAObjectType = require("../ua_object_type").UAObjectType;
- const UAObject = require("../ua_object").UAObject;
- const BaseNode = require("../base_node").BaseNode;
- const AttributeIds = require("node-opcua-data-model").AttributeIds;
- const NodeClass = require("node-opcua-data-model").NodeClass;
- const TimeZone = require("node-opcua-data-model").TimeZone;
- const UAStateMachine = require("../state_machine/finite_state_machine").UAStateMachine;
- const UATwoStateVariable = require("../ua_two_state_variable").UATwoStateVariable;
-
- const resolveNodeId = require("node-opcua-nodeid").resolveNodeId;
- const coerceLocalizedText = require("node-opcua-data-model").coerceLocalizedText;
- const LocalizedText = require("node-opcua-data-model").LocalizedText;
- const NodeId = require("node-opcua-nodeid").NodeId;
-
- const EventData = require("../address_space_add_event_type").EventData;
-
- const debugLog = require("node-opcua-debug").make_debugLog(__filename);
- const doDebug = require("node-opcua-debug").checkDebugFlag(__filename);
-
- const AddressSpace = require("../address_space").AddressSpace;
- const Namespace = require("../namespace").Namespace;
-
- const SessionContext = require("../session_context").SessionContext;
-
- const utils = require("node-opcua-utils");
-
- function _visit(self, node, prefix) {
- const aggregates = node.getAggregates();
-
- aggregates.forEach(function(aggregate) {
- if (aggregate instanceof UAVariable) {
- let name = aggregate.browseName.toString();
- name = utils.lowerFirstLetter(name);
-
- const key = prefix + name;
-
- // istanbul ignore next
- if (doDebug) {
- debugLog("adding key =", key);
- }
- self._map[key] = aggregate.readValue().value;
- self._node_index[key] = aggregate;
- _visit(self, aggregate, prefix + name + ".");
- }
- });
- }
- function _installOnChangeEventHandlers(self, node, prefix) {
- const aggregates = node.getAggregates();
-
- aggregates.forEach(function(aggregate) {
- if (aggregate instanceof UAVariable) {
- let name = aggregate.browseName.toString();
- name = utils.lowerFirstLetter(name);
-
- const key = prefix + name;
-
- // istanbul ignore next
- if (doDebug) {
- debugLog("adding key =", key);
- }
-
- aggregate.on("value_changed", function(newDataValue, oldDataValue) {
- self._map[key] = newDataValue.value;
- self._node_index[key] = aggregate;
- });
-
- _installOnChangeEventHandlers(self, aggregate, prefix + name + ".");
- }
- });
- }
- function _ensure_condition_values_correctness(self, node, prefix, error) {
- const displayError = !!error;
- error = error || [];
-
- const aggregates = node.getAggregates();
-
- aggregates.forEach(function(aggregate) {
- if (aggregate instanceof UAVariable) {
- let name = aggregate.browseName.toString();
- name = utils.lowerFirstLetter(name);
-
- const key = prefix + name;
-
- const snapshot_value = self._map[key].toString();
- const condition_value = aggregate.readValue().value.toString();
-
- if (snapshot_value !== condition_value) {
- error.push(
- " Condition Branch0 is not in sync with node values for " +
- key +
- "\n v1= " +
- snapshot_value +
- "\n v2= " +
- condition_value
- );
- }
-
- self._node_index[key] = aggregate;
- _ensure_condition_values_correctness(self, aggregate, prefix + name + ".", error);
- }
- });
- if (displayError && error.length) {
- throw new Error(error.join("\n"));
- }
- }
- function _record_condition_state(self, condition) {
- self._map = {};
- self._node_index = {};
- assert(condition instanceof UAConditionBase);
- _visit(self, condition, "");
- }
- /**
- * @class ConditionSnapshot
- * @extends EventEmitter
- * @param condition
- * @param branchId
- * @constructor
- */
- function ConditionSnapshot(condition, branchId) {
- const self = this;
- EventEmitter.call(this);
- if (condition && branchId) {
- assert(branchId instanceof NodeId);
- //xx self.branchId = branchId;
- self.condition = condition;
- self.eventData = new EventData(condition);
- // a nodeId/Variant map
- _record_condition_state(self, condition);
-
- if (branchId === NodeId.NullNodeId) {
- _installOnChangeEventHandlers(self, condition, "");
- }
-
- self._set_var("branchId", DataType.NodeId, branchId);
- }
- }
- util.inherits(ConditionSnapshot, EventEmitter);
-
- // /**
- // *
- // * @return {ConditionSnapshot}
- // */
- // ConditionSnapshot.prototype.clone = function () {
- // var self = this;
- // var clone = new ConditionSnapshot();
- // clone.branchId = self.branchId;
- // clone.condition = self.condition;
- // //xx clone.eventData = new EventData(clone.condition);
- // clone._map = _.clone(self._map);
- // return clone;
- // };
-
- const disabledVar = new Variant({
- dataType: "StatusCode",
- value: StatusCodes.BadConditionDisabled
- });
-
- ConditionSnapshot.prototype._constructEventData = function() {
- const self = this;
- const addressSpace = self.condition.addressSpace;
-
- if (self.branchId === NodeId.NullNodeId) {
- _ensure_condition_values_correctness(self, self.condition, "");
- }
-
- const isDisabled = !self.condition.getEnabledState();
- const eventData = new EventData(self.condition);
- Object.keys(self._map).forEach(function(key) {
- const node = self._node_index[key];
- if (isDisabled && !_varTable.hasOwnProperty(key)) {
- eventData.setValue(key, node, disabledVar);
- } else {
- eventData.setValue(key, node, self._map[key]);
- }
- });
-
- return eventData;
-
- // self.condition.getAggregates().forEach(function(child){
- // if (child instanceof UAVariable) {
- // var name = utils.lowerFirstLetter(child.browseName.toString());
- // self.eventData[name] =child.readValue().value;
- // }
- // });
- // return self.eventData.clone();
- };
-
- /**
- * @method resolveSelectClause
- * @param selectClause {SelectClause}
- */
- ConditionSnapshot.prototype.resolveSelectClause = function(selectClause) {
- const self = this;
- return self.eventData.resolveSelectClause(selectClause);
- };
-
- /**
- * @method readValue
- * @param nodeId {NodeId}
- * @param selectClause {SelectClause}
- * @return {Variant}
- */
- ConditionSnapshot.prototype.readValue = function(nodeId, selectClause) {
- const self = this;
-
- const isDisabled = !self.condition.getEnabledState();
- if (isDisabled) {
- return disabledVar;
- }
-
- const key = nodeId.toString();
- const variant = self._map[key];
- if (!variant) {
- // the value is not handled by us .. let's delegate
- // to the eventData helper object
- return self.eventData.readValue(nodeId, selectClause);
- }
- assert(variant instanceof Variant);
- return variant;
- };
-
- function normalizeName(str) {
- return str
- .split(".")
- .map(utils.lowerFirstLetter)
- .join(".");
- }
- ConditionSnapshot.normalizeName = normalizeName;
-
- // list of Condition variables that should not be published as BadConditionDisabled when the condition
- // is in a disabled state.
- var _varTable = {
- branchId: 1,
- eventId: 1,
- eventType: 1,
- sourceNode: 1,
- sourceName: 1,
- time: 1,
- enabledState: 1,
- "enabledState.id": 1,
- "enabledState.effectiveDisplayName": 1,
- "enabledState.transitionTime": 1,
- conditionClassId: 1,
- conditionClassName: 1,
- conditionName: 1
- };
- ConditionSnapshot.prototype._get_var = function(varName, dataType) {
- const self = this;
-
- if (!self.condition.getEnabledState() && !_varTable.hasOwnProperty(varName)) {
- console.log("ConditionSnapshot#_get_var condition enabled =", self.condition.getEnabledState());
- return disabledVar;
- }
-
- const key = normalizeName(varName);
- const variant = self._map[key];
- return variant.value;
- };
-
- ConditionSnapshot.prototype._set_var = function(varName, dataType, value) {
- const self = this;
-
- const key = normalizeName(varName);
- // istanbul ignore next
- if (!self._map.hasOwnProperty(key)) {
- if (doDebug) {
- debugLog(" cannot find node ".white.bold.bgRed + varName.cyan);
- debugLog(" map=", Object.keys(self._map).join(" "));
- }
- }
- self._map[key] = new Variant({
- dataType: dataType,
- value: value
- });
-
- if (self._map[key + ".sourceTimestamp"]) {
- self._map[key + ".sourceTimestamp"] = new Variant({
- dataType: DataType.DateTime,
- value: new Date()
- });
- }
-
- const variant = self._map[key];
- const node = self._node_index[key];
- assert(node instanceof UAVariable);
- self.emit("value_changed", node, variant);
- };
-
- /**
- * @method getBrandId
- * @return {NodeId}
- */
- ConditionSnapshot.prototype.getBranchId = function() {
- const self = this;
- return self._get_var("branchId", DataType.NodeId);
- };
-
- /**
- * @method getEventId
- * @return {ByteString}
- */
- ConditionSnapshot.prototype.getEventId = function() {
- const self = this;
- return self._get_var("eventId", DataType.ByteString);
- };
- /**
- * @method getRetain
- * @return {Boolean}
- */
- ConditionSnapshot.prototype.getRetain = function() {
- const self = this;
- return self._get_var("retain", DataType.Boolean);
- };
-
- /**
- *
- * @method setRetain
- * @param retainFlag {Boolean}
- */
- ConditionSnapshot.prototype.setRetain = function(retainFlag) {
- const self = this;
- retainFlag = !!retainFlag;
- return self._set_var("retain", DataType.Boolean, retainFlag);
- };
-
- /**
- * @method renewEventId
- *
- */
- ConditionSnapshot.prototype.renewEventId = function() {
- const self = this;
- const addressSpace = self.condition.addressSpace;
- // create a new event Id for this new condition
- const eventId = addressSpace.generateEventId();
- const ret = self._set_var("eventId", DataType.ByteString, eventId.value);
-
- //xx var branch = self; console.log("MMMMMMMMrenewEventId branch " + branch.getBranchId().toString() + " eventId = " + branch.getEventId().toString("hex"));
-
- return ret;
- };
-
- /**
- * @method getEnabledState
- * @return {Boolean}
- */
- ConditionSnapshot.prototype.getEnabledState = function() {
- const self = this;
- return self._get_twoStateVariable("enabledState");
- };
- /**
- * @method setEnabledState
- * @param value {Boolean}
- * @return void
- */
- ConditionSnapshot.prototype.setEnabledState = function(value) {
- const self = this;
- return self._set_twoStateVariable("enabledState", value);
- };
- /**
- * @method getEnabledStateAsString
- * @return {String}
- */
- ConditionSnapshot.prototype.getEnabledStateAsString = function() {
- const self = this;
- return self._get_var("enabledState", DataType.LocalizedText).text;
- };
-
- /**
- * @method getComment
- * @return {LocalizedText}
- */
- ConditionSnapshot.prototype.getComment = function() {
- const self = this;
- return self._get_var("comment", DataType.LocalizedText);
- };
-
- /**
- * Set condition comment
- *
- * Comment contains the last comment provided for a certain state (ConditionBranch). It may
- * have been provided by an AddComment Method, some other Method or in some other
- * manner. The initial value of this Variable is null, unless it is provided in some other manner. If
- * a Method provides as an option the ability to set a Comment, then the value of this Variable is
- * reset to null if an optional comment is not provided.
- *
- * @method setComment
- * @param txtMessage {LocalizedText}
- */
- ConditionSnapshot.prototype.setComment = function(txtMessage) {
- const self = this;
- assert(txtMessage);
- txtMessage = coerceLocalizedText(txtMessage);
- self._set_var("comment", DataType.LocalizedText, txtMessage);
- /*
- * OPCUA Spec 1.0.3 - Part 9:
- * Comment, severity and quality are important elements of Conditions and any change
- * to them will cause Event Notifications.
- *
- */
- self._need_event_raise = true;
- };
-
- /**
- *
- * @method setMessage
- * @param txtMessage {LocalizedText}
- */
- ConditionSnapshot.prototype.setMessage = function(txtMessage) {
- const self = this;
- assert(txtMessage);
- txtMessage = coerceLocalizedText(txtMessage);
- return self._set_var("message", DataType.LocalizedText, txtMessage);
- };
-
- /**
- * @method setClientUserId
- * @param userIdentity {String}
- */
- ConditionSnapshot.prototype.setClientUserId = function(userIdentity) {
- const self = this;
- return self._set_var("clientUserId", DataType.String, userIdentity.toString());
- };
-
- /*
- *
- *
- * as per spec 1.0.3 - Part 9
- *
- * Quality reveals the status of process values or other resources that this Condition instance is
- * based upon. If, for example, a process value is “Uncertain”, the associated “LevelAlarm”
- * Condition is also questionable. Values for the Quality can be any of the OPC StatusCodes
- * defined in Part 8 as well as Good, Uncertain and Bad as defined in Part 4. These
- * StatusCodes are similar to but slightly more generic than the description of data quality in the
- * various field bus specifications. It is the responsibility of the Server to map internal status
- * information to these codes. A Server which supports no quality information shall return Good.
- * This quality can also reflect the communication status associated with the system that this
- * value or resource is based on and from which this Alarm was received. For communication
- * errors to the underlying system, especially those that result in some unavailable Event fields,
- * the quality shall be Bad_NoCommunication error.
- *
- * Quality refers to the quality of the data value(s) upon which this Condition is based. Since a
- * Condition is usually based on one or more Variables, the Condition inherits the quality of
- * these Variables. E.g., if the process value is “Uncertain”, the “LevelAlarm” Condition is also
- * questionable. If more than one variable is represented by a given condition or if the condition
- * is from an underlining system and no direct mapping to a variable is available, it is up to the
- * application to determine what quality is displayed as part of the condition.
- */
-
- /**
- * set the condition quality
- * @method setQuality
- * @param quality {StatusCode}
- */
- ConditionSnapshot.prototype.setQuality = function(quality) {
- const self = this;
- assert(quality instanceof StatusCode);
- assert(quality.hasOwnProperty("value") || "quality must be a StatusCode");
- self._set_var("quality", DataType.StatusCode, quality);
- /*
- * OPCUA Spec 1.0.3 - Part 9:
- * Comment, severity and quality are important elements of Conditions and any change
- * to them will cause Event Notifications.
- *
- */
- self._need_event_raise = true;
- };
-
- /**
- * @method getQuality
- * @return {StatusCode}
- */
- ConditionSnapshot.prototype.getQuality = function() {
- const self = this;
- return self._get_var("quality", DataType.StatusCode);
- };
-
- /*
- * as per spec 1.0.3 - Part 9
- * The Severity of a Condition is inherited from the base Event model defined in Part 5. It
- * indicates the urgency of the Condition and is also commonly called ‘priority’, especially in
- * relation to Alarms in the ProcessConditionClass.
- *
- * as per spec 1.0.3 - PArt 5
- * Severity is an indication of the urgency of the Event. This is also commonly called “priority”.
- * Values will range from 1 to 1 000, with 1 being the lowest severity and 1 000 being the highest.
- * Typically, a severity of 1 would indicate an Event which is informational in nature, while a value
- * of 1 000 would indicate an Event of catastrophic nature, which could potentially result in severe
- * financial loss or loss of life.
- * It is expected that very few Server implementations will support 1 000 distinct severity levels.
- * Therefore, Server developers are responsible for distributing their severity levels across the
- * 1 to 1 000 range in such a manner that clients can assume a linear distribution. For example, a
- * client wishing to present five severity levels to a user should be able to do the following
- * mapping:
- * Client Severity OPC Severity
- * HIGH 801 – 1 000
- * MEDIUM HIGH 601 – 800
- * MEDIUM 401 – 600
- * MEDIUM LOW 201 – 400
- * LOW 1 – 200
- * In many cases a strict linear mapping of underlying source severities to the OPC Severity range
- * is not appropriate. The Server developer will instead intelligently map the underlying source
- * severities to the 1 to 1 000 OPC Severity range in some other fashion. In particular, it is
- * recommended that Server developers map Events of high urgency into the OPC severity range
- * of 667 to 1 000, Events of medium urgency into the OPC severity range of 334 to 666 and
- * Events of low urgency into OPC severities of 1 to 333.
- */
- /**
- * @method setSeverity
- * @param severity {UInt16}
- */
- ConditionSnapshot.prototype.setSeverity = function(severity) {
- const self = this;
- assert(_.isFinite(severity), "expecting a UInt16");
-
- // record automatically last severity
- const lastSeverity = self.getSeverity();
- self.setLastSeverity(lastSeverity);
- self._set_var("severity", DataType.UInt16, severity);
- /*
- * OPCUA Spec 1.0.3 - Part 9:
- * Comment, severity and quality are important elements of Conditions and any change
- * to them will cause Event Notifications.
- *
- */
- self._need_event_raise = true;
- };
-
- /**
- * @method getSeverity
- * @return {UInt16}
- */
- ConditionSnapshot.prototype.getSeverity = function() {
- const self = this;
- assert(self.condition.getEnabledState(), "condition must be enabled");
- const value = self._get_var("severity", DataType.UInt16);
- return +value;
- };
-
- /*
- * as per spec 1.0.3 - part 9:
- * LastSeverity provides the previous severity of the ConditionBranch. Initially this Variable
- * contains a zero value; it will return a value only after a severity change. The new severity is
- * supplied via the Severity Property which is inherited from the BaseEventType.
- *
- */
- /**
- * @method setLastSeverity
- * @param severity {UInt16}
- */
- ConditionSnapshot.prototype.setLastSeverity = function(severity) {
- const self = this;
- severity = +severity;
- return self._set_var("lastSeverity", DataType.UInt16, severity);
- };
- /**
- * @method getLastSeverity
- * @return {UInt16}
- */
- ConditionSnapshot.prototype.getLastSeverity = function() {
- const self = this;
- const value = self._get_var("lastSeverity", DataType.UInt16);
- return +value;
- };
-
- /**
- * setReceiveTime
- *
- * (as per OPCUA 1.0.3 part 5)
- *
- * ReceiveTime provides the time the OPC UA Server received the Event from the underlying
- * device of another Server.
- *
- * ReceiveTime is analogous to ServerTimestamp defined in Part 4, i.e.
- * in the case where the OPC UA Server gets an Event from another OPC UA Server, each Server
- * applies its own ReceiveTime. That implies that a Client may get the same Event, having the
- * same EventId, from different Servers having different values of the ReceiveTime.
- *
- * The ReceiveTime shall always be returned as value and the Server is not allowed to return a
- * StatusCode for the ReceiveTime indicating an error.
- *
- * @method setReceiveTime
- * @param time {Date} : UTCTime
- */
- ConditionSnapshot.prototype.setReceiveTime = function(time) {
- assert(time instanceof Date);
- const self = this;
- return self._set_var("receiveTime", DataType.DateTime, time);
- };
-
- /**
- * (as per OPCUA 1.0.3 part 5)
-
- * Time provides the time the Event occurred. This value is set as close to the event generator as
- * possible. It often comes from the underlying system or device. Once set, intermediate OPC UA
- * Servers shall not alter the value.
- *
- * @method setTime
- * @param time {Date}
- */
- ConditionSnapshot.prototype.setTime = function(time) {
- assert(time instanceof Date);
- const self = this;
- return self._set_var("time", DataType.DateTime, time);
- };
-
- /**
- * LocalTime is a structure containing the Offset and the DaylightSavingInOffset flag. The Offset
- * specifies the time difference (in minutes) between the Time Property and the time at the location
- * in which the event was issued. If DaylightSavingInOffset is TRUE, then Standard/Daylight
- * savings time (DST) at the originating location is in effect and Offset includes the DST c orrection.
- * If FALSE then the Offset does not include DST correction and DST may or may not have been
- * in effect.
- * @method setLocalTime
- * @param localTime {TimeZone}
- */
- ConditionSnapshot.prototype.setLocalTime = function(localTime) {
- assert(localTime instanceof TimeZone);
- const self = this;
- return self._set_var("localTime", DataType.ExtensionObject, new TimeZone(localTime));
- };
- // read only !
- ConditionSnapshot.prototype.getSourceName = function() {
- return this._get_var("sourceName", DataType.LocalizedText);
- };
-
- /**
- * @method getSourceNode
- * return {NodeId}
- */
- ConditionSnapshot.prototype.getSourceNode = function() {
- return this._get_var("sourceNode", DataType.NodeId);
- };
-
- /**
- * @method getEventType
- * return {NodeId}
- */
- ConditionSnapshot.prototype.getEventType = function() {
- return this._get_var("eventType", DataType.NodeId);
- };
-
- /**
- * @method getMessage
- * return {LocalizedText}
- */
- ConditionSnapshot.prototype.getMessage = function() {
- return this._get_var("message", DataType.LocalizedText);
- };
-
- ConditionSnapshot.prototype.isCurrentBranch = function() {
- return this._get_var("branchId") === NodeId.NullNodeId;
- };
-
- /**
- * @class ConditionSnapshot
- * @param varName
- * @param value
- * @private
- */
- ConditionSnapshot.prototype._set_twoStateVariable = function(varName, value) {
- value = !!value;
- const self = this;
-
- const hrKey = ConditionSnapshot.normalizeName(varName);
- const idKey = ConditionSnapshot.normalizeName(varName) + ".id";
-
- const variant = new Variant({ dataType: DataType.Boolean, value: value });
- self._map[idKey] = variant;
-
- // also change varName with human readable text
- const twoStateNode = self._node_index[hrKey];
- if (!twoStateNode) {
- throw new Error("Cannot find twoState Varaible with name " + varName);
- }
- if (!(twoStateNode instanceof UATwoStateVariable)) {
- throw new Error("Cannot find twoState Varaible with name " + varName + " " + twoStateNode);
- }
-
- const txt = value ? twoStateNode._trueState : twoStateNode._falseState;
-
- const hrValue = new Variant({
- dataType: DataType.LocalizedText,
- value: coerceLocalizedText(txt)
- });
- self._map[hrKey] = hrValue;
-
- const node = self._node_index[idKey];
-
- // also change ConditionNode if we are on currentBranch
- if (self.isCurrentBranch()) {
- assert(twoStateNode instanceof UATwoStateVariable);
- twoStateNode.setValue(value);
- //xx console.log("Is current branch", twoStateNode.toString(),variant.toString());
- //xx console.log(" = ",twoStateNode.getValue());
- }
- self.emit("value_changed", node, variant);
- };
-
- ConditionSnapshot.prototype._get_twoStateVariable = function(varName) {
- const self = this;
- const key = ConditionSnapshot.normalizeName(varName) + ".id";
- const variant = self._map[key];
-
- // istanbul ignore next
- if (!variant) {
- throw new Error("Cannot find TwoStateVariable with name " + varName);
- }
- return variant.value;
- };
- exports.ConditionSnapshot = ConditionSnapshot;
-
- /**
- * @class BaseEventType
- * @class UAObject
- * @constructor
- */
- function BaseEventType() {}
- util.inherits(BaseEventType, UAObject);
-
- /**
- * @method setSourceName
- * @param name
- */
- BaseEventType.prototype.setSourceName = function(name) {
- assert(typeof name === "string");
- const self = this;
- self.sourceName.setValueFromSource(
- new Variant({
- dataType: DataType.String,
- value: name
- })
- );
- };
-
- /**
- * @method setSourceNode
- * @param node {NodeId|UAObject}
- */
- BaseEventType.prototype.setSourceNode = function(node) {
- const self = this;
- self.sourceNode.setValueFromSource(
- new Variant({
- dataType: DataType.NodeId,
- value: node.nodeId ? node.nodeId : node
- })
- );
- };
-
- /**
- * @class UAConditionBase
- * @constructor
- * @extends BaseEventType
- */
- function UAConditionBase() {}
- util.inherits(UAConditionBase, BaseEventType);
- UAConditionBase.prototype.nodeClass = NodeClass.Object;
- UAConditionBase.typeDefinition = resolveNodeId("ConditionType");
-
- /**
- * @method initialize
- * @private
- */
- UAConditionBase.prototype.initialize = function() {
- const self = this;
- self._branches = {};
- };
-
- /**
- * @method post_initialize
- * @private
- */
- UAConditionBase.prototype.post_initialize = function() {
- const self = this;
- assert(!self._branch0);
- self._branch0 = new ConditionSnapshot(self, NodeId.NullNodeId);
-
- // the condition OPCUA object alway reflects the default branch states
- // so we set a mechanism that automatically keeps self in sync
- // with the default branch.
-
- // the implication of this convention is that interacting with the condition variable
- // shall be made by using branch0, any value change made
- // using the standard setValueFromSource mechanism will not be work properly.
- self._branch0.on("value_changed", function(node, variant) {
- assert(node instanceof UAVariable);
- node.setValueFromSource(variant);
- });
- };
-
- /**
- * @method getBranchCount
- * @return {Number}
- */
- UAConditionBase.prototype.getBranchCount = function() {
- const self = this;
- return Object.keys(self._branches).length;
- };
- UAConditionBase.prototype.getBranches = function() {
- const self = this;
- return Object.keys(self._branches).map(function(x) {
- return self._branches[x];
- });
- };
- UAConditionBase.prototype.getBranchIds = function() {
- const self = this;
- return self.getBranches().map(function(b) {
- return b.getBranchId();
- });
- };
- const ec = require("node-opcua-basic-types");
- const randomGuid = ec.randomGuid;
- const makeNodeId = require("node-opcua-nodeid").makeNodeId;
-
- function _create_new_branch_id() {
- return makeNodeId(randomGuid(), 1);
- }
-
- /**
- * @method createBranch
- * @return {ConditionSnapshot}
- */
- UAConditionBase.prototype.createBranch = function() {
- const self = this;
- const branchId = _create_new_branch_id();
- const snapshot = new ConditionSnapshot(self, branchId);
- self._branches[branchId.toString()] = snapshot;
- return snapshot;
- };
- /**
- * @method deleteBranch
- * @param branch {ConditionSnapshot}
- */
- UAConditionBase.prototype.deleteBranch = function(branch) {
- const self = this;
- const key = branch.getBranchId().toString();
- assert(branch.getBranchId() !== NodeId.NullNodeId, "cannot delete branch zero");
- assert(self._branches.hasOwnProperty(key));
- delete self._branches[key];
- self.emit("branch_deleted", key);
- };
-
- const minDate = new Date(1600, 1, 1);
-
- function prepare_date(sourceTimestamp) {
- if (!sourceTimestamp || !sourceTimestamp.value) {
- return minDate;
- }
- assert(sourceTimestamp.value instanceof Date);
- return sourceTimestamp;
- }
-
- function _update_sourceTimestamp(dataValue /*, indexRange*/) {
- const self = this;
- //xx console.log("_update_sourceTimestamp = "+self.nodeId.toString().cyan+ " " + self.browseName.toString(), self.sourceTimestamp.nodeId.toString().cyan + " " + dataValue.sourceTimestamp);
- self.sourceTimestamp.setValueFromSource({
- dataType: DataType.DateTime,
- value: dataValue.sourceTimestamp
- });
- }
- const makeAccessLevel = require("node-opcua-data-model").makeAccessLevel;
-
- function _install_condition_variable_type(node) {
- assert(node instanceof BaseNode);
- // from spec 1.03 : 5.3 condition variables
- // However, a change in their value is considered important and supposed to trigger
- // an Event Notification. These information elements are called ConditionVariables.
- if (node.sourceTimestamp) {
- node.sourceTimestamp.accessLevel = makeAccessLevel("CurrentRead");
- } else {
- console.warn("cannot find node.sourceTimestamp", node.browseName.toString());
- }
- node.accessLevel = makeAccessLevel("CurrentRead");
-
- // from spec 1.03 : 5.3 condition variables
- // a condition VariableType has a sourceTimeStamp exposed property
- // SourceTimestamp indicates the time of the last change of the Value of this ConditionVariable.
- // It shall be the same time that would be returned from the Read Service inside the DataValue
- // structure for the ConditionVariable Value Attribute.
-
- assert(node.typeDefinitionObj.browseName.toString() === "ConditionVariableType");
- assert(node.sourceTimestamp.browseName.toString() === "SourceTimestamp");
- node.on("value_changed", _update_sourceTimestamp);
- }
-
- /**
- * @method getEnabledState
- * @return {Boolean}
- */
- UAConditionBase.prototype.getEnabledState = function() {
- const conditionNode = this;
- return conditionNode.enabledState.getValue();
- };
- /**
- * @method getEnabledStateAsString
- * @return {String}
- */
- UAConditionBase.prototype.getEnabledStateAsString = function() {
- const conditionNode = this;
- return conditionNode.enabledState.getValueAsString();
- };
-
- UAConditionBase.prototype.evaluateConditionsAfterEnabled = function() {
- assert(this.getEnabledState() === true);
- throw new Error("Unimplemented , please override");
- };
-
- /**
- * @method _setEnabledState
- * @param requestedEnabledState {Boolean}
- * @return {StatusCode} StatusCodes.Good if successful or BadConditionAlreadyEnabled/BadConditionAlreadyDisabled
- * @private
- */
- UAConditionBase.prototype._setEnabledState = function(requestedEnabledState) {
- const conditionNode = this;
-
- assert(_.isBoolean(requestedEnabledState));
-
- const enabledState = conditionNode.getEnabledState();
- if (enabledState && requestedEnabledState) {
- return StatusCodes.BadConditionAlreadyEnabled;
- }
- if (!enabledState && !requestedEnabledState) {
- return StatusCodes.BadConditionAlreadyDisabled;
- }
-
- conditionNode._branch0.setEnabledState(requestedEnabledState);
- //conditionNode.enabledState.setValue(requestedEnabledState);
-
- //xx assert(conditionNode.enabledState.id.readValue().value.value === requestedEnabledState,"sanity check 1");
- //xx assert(conditionNode.currentBranch().getEnabledState() === requestedEnabledState,"sanity check 2");
-
- if (!requestedEnabledState) {
- // as per Spec 1.0.3 part 9:
- //* When the Condition instance enters the Disabled state, the Retain Property of this
- // Condition shall be set to FALSE by the Server to indicate to the Client that the
- // Condition instance is currently not of interest to Clients.
- // TODO : shall we really set retain to false or artificially expose the retain false as false
- // whist enabled state is false ?
- conditionNode._previousRetainFlag = conditionNode.currentBranch().getRetain();
- conditionNode.currentBranch().setRetain(false);
-
- // install the mechanism by which all condition values will be return
- // as Null | BadConditionDisabled;
- const statusCode = StatusCodes.BadConditionDisabled;
-
- // a notification must be send
- conditionNode.raiseConditionEvent(conditionNode.currentBranch(), true);
- } else {
- //* When the Condition instance enters the enabled state, the Condition shall be
- // evaluated and all of its Properties updated to reflect the current values. If this
- // evaluation causes the Retain Property to transition to TRUE for any ConditionBranch,
- // then an Event Notification shall be generated for that ConditionBranch.
-
- conditionNode.evaluateConditionsAfterEnabled();
-
- // todo evaluate branches
- // conditionNode.evaluateBranches();
-
- // restore retain flag
- if (conditionNode.hasOwnProperty("_previousRetainFlag")) {
- conditionNode.currentBranch().setRetain(conditionNode._previousRetainFlag);
- }
-
- // todo send notification for branches with retain = true
- let nb_condition_resent = 0;
- if (conditionNode.currentBranch().getRetain()) {
- nb_condition_resent += conditionNode._resend_conditionEvents();
- }
-
- if (nb_condition_resent === 0) {
- // a notification must be send
- conditionNode.raiseConditionEvent(conditionNode.currentBranch(), true);
- }
- }
- return StatusCodes.Good;
- };
-
- /**
- *
- * @method setEnabledState
- * @param requestedEnabledState {Boolean}
- * @private
- */
- UAConditionBase.prototype.setEnabledState = function(requestedEnabledState) {
- return this._setEnabledState(requestedEnabledState);
- };
-
- /**
- * @method setReceiveTime
- * @param time {Date}
- */
- UAConditionBase.prototype.setReceiveTime = function(time) {
- const self = this;
- return self._branch0.setReceiveTime(time);
- };
-
- /**
- * @method setLocalTime
- * @param time {Date}
- */
- UAConditionBase.prototype.setLocalTime = function(time) {
- const self = this;
- return self._branch0.setLocalTime(time);
- };
-
- /**
- * @method setTime
- * @param time {Date}
- */
- UAConditionBase.prototype.setTime = function(time) {
- const self = this;
- return self._branch0.setTime(time);
- };
-
- UAConditionBase.prototype._assert_valid = function() {
- const self = this;
- assert(self.receiveTime.readValue().value.dataType === DataType.DateTime);
- assert(self.receiveTime.readValue().value.value instanceof Date);
-
- assert(self.localTime.readValue().value.dataType === DataType.ExtensionObject);
- assert(self.message.readValue().value.dataType === DataType.LocalizedText);
- assert(self.severity.readValue().value.dataType === DataType.UInt16);
-
- assert(self.time.readValue().value.dataType === DataType.DateTime);
- assert(self.time.readValue().value.value instanceof Date);
-
- assert(self.quality.readValue().value.dataType === DataType.StatusCode);
- assert(self.enabledState.readValue().value.dataType === DataType.LocalizedText);
- assert(self.branchId.readValue().value.dataType === DataType.NodeId);
- };
-
- const browse_service = require("node-opcua-service-browse");
- const BrowseDirection = require("node-opcua-data-model").BrowseDirection;
-
- /**
- * @method conditionOfNode
- * @return {UAObject}
- */
- UAConditionBase.prototype.conditionOfNode = function() {
- const refs = this.findReferencesExAsObject("HasCondition", BrowseDirection.Inverse);
- if (refs.length === 0) {
- return null;
- }
- assert(refs.length !== 0, "UAConditionBase must be the condition of some node");
- assert(refs.length === 1, "expecting only one ConditionOf");
- const node = refs[0];
- assert(
- node instanceof UAObject || node instanceof UAVariable,
- "node for which we are the condition shall be an UAObject or UAVariable"
- );
- return node;
- };
-
- /**
- * @method raiseConditionEvent
- * Raise a Instance Event
- * (see also UAObject#raiseEvent to raise a transient event)
- * @param branch {ConditionSnapshot}
- */
- UAConditionBase.prototype.raiseConditionEvent = function(branch, renewEventId) {
- assert(arguments.length === 2, "expecting 2 arguments");
- if (renewEventId) {
- branch.renewEventId();
- }
-
- //xx console.log("MMMMMMMM%%%%%%%%%%%%%%%%%%%%% branch " + branch.getBranchId().toString() + " eventId = " + branch.getEventId().toString("hex"));
-
- assert(branch instanceof ConditionSnapshot);
- const self = this;
- self._assert_valid();
-
- // In fact the event is raised by the object of which we are the condition
- const conditionOfNode = self.conditionOfNode();
-
- if (conditionOfNode) {
- const eventData = branch._constructEventData();
-
- self.emit("event", eventData);
-
- if (conditionOfNode instanceof UAObject) {
- //xx assert(conditionOfNode.eventNotifier === 0x01);
- conditionOfNode._bubble_up_event(eventData);
- } else {
- assert(conditionOfNode instanceof UAVariable);
- // in this case
- const eventOfs = conditionOfNode.getEventSourceOfs();
- assert(eventOfs.length === 1);
- const node = eventOfs[0];
- assert(node instanceof UAObject);
- node._bubble_up_event(eventData);
- }
- }
- //xx console.log("MMMMMMMM%%%%%%%%%%%%%%%%%%%%% branch " + branch.getBranchId().toString() + " eventId = " + branch.getEventId().toString("hex"));
- };
-
- /**
- * @class ConditionInfo
- * @param options {Object}
- * @param options.message {String|LocalizedText} the event message
- * @param options.severity {UInt16} severity
- * @param options.quality {StatusCode} quality
- * @param options.retain {Boolean} retain flag
- * @constructor
- */
- function ConditionInfo(options) {
- this.severity = null;
- this.quality = null;
- this.message = null;
- this.retain = null;
-
- if (options.hasOwnProperty("message") && options.message !== null) {
- options.message = LocalizedText.coerce(options.message);
- assert(options.message instanceof LocalizedText);
- this.message = options.message;
- }
- if (options.hasOwnProperty("quality") && options.quality !== null) {
- this.quality = options.quality;
- }
- if (options.hasOwnProperty("severity") && options.severity !== null) {
- assert(_.isNumber(options.severity));
- this.severity = options.severity;
- }
- if (options.hasOwnProperty("retain") && options.retain !== null) {
- assert(_.isBoolean(options.retain));
- this.retain = options.retain;
- }
- }
- /**
- * @method isDifferentFrom
- * @param otherConditionInfo {ConditionInfo}
- * @return {Boolean}
- */
- ConditionInfo.prototype.isDifferentFrom = function(otherConditionInfo) {
- return (
- this.severity !== otherConditionInfo.severity ||
- this.quality !== otherConditionInfo.quality ||
- this.message !== otherConditionInfo.message
- );
- };
- exports.ConditionInfo = ConditionInfo;
-
- UAConditionBase.defaultSeverity = 250;
- /**
- *
- * @method raiseNewCondition
- * @param conditionInfo {ConditionInfo}
- *
- */
- UAConditionBase.prototype.raiseNewCondition = function(conditionInfo) {
- const self = this;
- if (!self.getEnabledState()) {
- throw new Error("UAConditionBase#raiseNewCondition Condition is not enabled");
- }
-
- conditionInfo = conditionInfo || {};
-
- conditionInfo.severity = conditionInfo.hasOwnProperty("severity")
- ? conditionInfo.severity
- : UAConditionBase.defaultSeverity;
-
- //only valid for ConditionObjects
- // todo check that object is of type ConditionType
-
- const addressSpace = self.addressSpace;
-
- const selfConditionType = self.typeDefinitionObj;
- const conditionType = addressSpace.findObjectType("ConditionType");
-
- assert(selfConditionType.isSupertypeOf(conditionType));
-
- const branch = self.currentBranch();
-
- const now = new Date();
- // install the eventTimestamp
- // set the received Time
- branch.setTime(now);
- branch.setReceiveTime(now);
- branch.setLocalTime(
- new TimeZone({
- offset: 0,
- daylightSavingInOffset: false
- })
- );
-
- if (conditionInfo.hasOwnProperty("message") && conditionInfo.message) {
- branch.setMessage(conditionInfo.message);
- }
- // todo receive time : when the server received the event from the underlying system.
- // self.receiveTime.setValueFromSource();
-
- if (conditionInfo.hasOwnProperty("severity") && conditionInfo.severity !== null) {
- assert(_.isFinite(conditionInfo.severity));
- branch.setSeverity(conditionInfo.severity);
- }
- if (conditionInfo.hasOwnProperty("quality") && conditionInfo.quality !== null) {
- assert(conditionInfo.quality instanceof StatusCode);
- branch.setQuality(conditionInfo.quality);
- }
- if (conditionInfo.hasOwnProperty("retain") && conditionInfo.retain !== null) {
- assert(_.isBoolean(conditionInfo.retain));
- branch.setRetain(!!conditionInfo.retain);
- }
-
- self.raiseConditionEvent(branch, true);
- };
-
- UAConditionBase.prototype.raiseNewBranchState = function(branch) {
- const self = this;
- self.raiseConditionEvent(branch, true);
-
- if (branch.getBranchId() !== NodeId.NullNodeId && !branch.getRetain()) {
- //xx console.log(" Deleting not longer needed branch ", branch.getBranchId().toString());
- // branch can be deleted
- self.deleteBranch(branch);
- }
- };
-
- function sameBuffer(b1, b2) {
- if (!b1 && !b2) {
- return true;
- }
- if (b1 && !b2) {
- return false;
- }
- if (!b1 && b2) {
- return false;
- }
- assert(b1 instanceof Buffer);
- assert(b2 instanceof Buffer);
- if (b1.length !== b2.length) {
- return false;
- }
- /*
- var bb1 = (Buffer.from(b1)).toString("hex");
- var bb2 = (Buffer.from(b2)).toString("hex");
- return bb1 === bb2;
- */
- const n = b1.length;
- for (let i = 0; i < n; i++) {
- if (b1[i] !== b2[i]) {
- return false;
- }
- }
- return true;
- }
- UAConditionBase.prototype._findBranchForEventId = function(eventId) {
- const conditionNode = this;
- if (sameBuffer(conditionNode.eventId.readValue().value.value, eventId)) {
- return conditionNode.currentBranch();
- }
- const e = _.filter(conditionNode._branches, function(branch, key) {
- return sameBuffer(branch.getEventId(), eventId);
- });
- if (e.length === 1) {
- return e[0];
- }
- assert(e.length === 0, "cannot have 2 branches with same eventId");
- return null; // not found
- };
-
- exports.UAConditionBase = UAConditionBase;
-
- /**
- * @method _raiseAuditConditionCommentEvent
- * @param sourceName {string}
- * @param eventId {Buffer}
- * @param comment {LocalizedText}
- * @private
- */
- UAConditionBase.prototype._raiseAuditConditionCommentEvent = function(sourceName, eventId, comment) {
- assert(eventId === null || eventId instanceof Buffer);
- assert(comment instanceof LocalizedText);
- const server = this.addressSpace.rootFolder.objects.server;
-
- const now = new Date();
-
- //xx if (true || server.isAuditing) {
- // ----------------------------------------------------------------------------------------------------------------
- server.raiseEvent("AuditConditionCommentEventType", {
- // AuditEventType
- /* part 5 - 6.4.3 AuditEventType */
- actionTimeStamp: {
- dataType: "DateTime",
- value: now
- },
- status: {
- dataType: "Boolean",
- value: true
- },
-
- serverId: {
- dataType: "String",
- value: ""
- },
-
- // ClientAuditEntryId contains the human-readable AuditEntryId defined in Part 3.
- clientAuditEntryId: {
- dataType: "String",
- value: ""
- },
-
- // The ClientUserId identifies the user of the client requesting an action. The ClientUserId can be
- // obtained from the UserIdentityToken passed in the ActivateSession call.
- clientUserId: {
- dataType: "String",
- value: ""
- },
- sourceName: {
- dataType: "String",
- value: sourceName
- },
-
- // AuditUpdateMethodEventType
- methodId: {},
- inputArguments: {},
- // AuditConditionCommentEventType
- eventId: {
- dataType: DataType.ByteString,
- value: eventId
- },
- comment: {
- dataType: DataType.LocalizedText,
- value: comment
- }
- });
- //xx }
- };
-
- /**
- * @method currentBranch
- * @return {ConditionSnapshot}
- */
- UAConditionBase.prototype.currentBranch = function() {
- return this._branch0;
- };
-
- /**
- *
- * Helper method to handle condition methods that takes a branchId and a comment
- * @method with_condition_method$
- * @param inputArguments {Array<Variant>}
- * @param context {Object}
- * @param context.object {BaseNode}
- * @param callback {Function}
- * @param callback.err {Error|null}
- * @param callback.result {Object}
- * @param callback.result.statusCode {StatusCode}
- * @param inner_func {Function}
- * @param inner_func.eventId {Buffer|null}
- * @param inner_func.comment {LocalizedText}
- * @param inner_func.branch {ConditionSnapshot}
- * @param inner_func.conditionNode {UAConditionBase}
- *
- * @return {void}
- */
- UAConditionBase.with_condition_method = function(inputArguments, context, callback, inner_func) {
- const conditionNode = context.object;
-
- //xx console.log(inputArguments.map(function(a){return a.toString()}));
- if (!(conditionNode instanceof UAConditionBase)) {
- callback(null, {
- statusCode: StatusCodes.BadNodeIdInvalid
- });
- return;
- }
-
- if (!conditionNode.getEnabledState()) {
- callback(null, {
- statusCode: StatusCodes.BadConditionDisabled
- });
- return;
- }
-
- // inputArguments has 2 arguments
- // EventId => ByteString The Identifier of the event to comment
- // Comment => LocalizedText The Comment to add to the condition
- assert(inputArguments.length === 2);
- assert(inputArguments[0].dataType === DataType.ByteString);
- assert(inputArguments[1].dataType === DataType.LocalizedText);
-
- const eventId = inputArguments[0].value;
- assert(!eventId || eventId instanceof Buffer);
-
- const comment = inputArguments[1].value;
- assert(comment instanceof LocalizedText);
-
- const branch = conditionNode._findBranchForEventId(eventId);
- if (!branch) {
- callback(null, {
- statusCode: StatusCodes.BadEventIdUnknown
- });
- return;
- }
- assert(branch instanceof ConditionSnapshot);
-
- const statusCode = inner_func(eventId, comment, branch, conditionNode);
-
- // record also who did the call
- branch.setClientUserId(context.userIdentity || "<unknown client user id>");
-
- callback(null, {
- statusCode: statusCode
- });
- };
-
- UAConditionBase.prototype._resend_conditionEvents = function() {
- // for the time being , only current branch
- const self = this;
- const currentBranch = self.currentBranch();
- if (currentBranch.getRetain()) {
- debugLog(" resending condition event for " + self.browseName.toString());
- self.raiseConditionEvent(currentBranch, false);
- return 1;
- }
- return 0;
- };
-
- BaseNode.prototype._conditionRefresh = function(_cache) {
- // visit all notifiers recursively
- _cache = _cache || {};
- const self = this;
- const notifiers = self.getNotifiers();
- const eventSources = self.getEventSources();
-
- const conditions = this.findReferencesAsObject("HasCondition", true);
- let i;
-
- for (i = 0; i < conditions.length; i++) {
- const condition = conditions[i];
- if (condition instanceof UAConditionBase) {
- condition._resend_conditionEvents();
- }
- }
- const arr = [].concat(notifiers, eventSources);
-
- for (i = 0; i < arr.length; i++) {
- const notifier = arr[i];
- const key = notifier.nodeId.toString();
- if (!_cache[key]) {
- _cache[key] = notifier;
- if (notifier._conditionRefresh) {
- notifier._conditionRefresh(_cache);
- }
- }
- }
- };
-
- function _perform_condition_refresh(addressSpace, inputArguments, context) {
- // --- possible StatusCodes:
- //
- // Bad_SubscriptionIdInvalid See Part 4 for the description of this result code
- // Bad_RefreshInProgress See Table 74 for the description of this result code
- // Bad_UserAccessDenied The Method was not called in the context of the Session
- // that owns the Subscription
- //
-
- // istanbul ignore next
- if (addressSpace._condition_refresh_in_progress) {
- // a refresh operation is already in progress....
- return StatusCodes.BadRefreshInProgress;
- }
-
- addressSpace._condition_refresh_in_progress = true;
-
- const server = context.object.addressSpace.rootFolder.objects.server;
- assert(server instanceof UAObject);
-
- const refreshStartEventType = addressSpace.findEventType("RefreshStartEventType");
- const refreshEndEventType = addressSpace.findEventType("RefreshEndEventType");
-
- assert(refreshStartEventType instanceof UAObjectType);
- assert(refreshEndEventType instanceof UAObjectType);
-
- server.raiseEvent(refreshStartEventType, {});
- // todo : resend retained conditions
-
- // starting from server object ..
- // evaluated all --> hasNotifier/hasEventSource -> node
- server._conditionRefresh();
-
- server.raiseEvent(refreshEndEventType, {});
-
- addressSpace._condition_refresh_in_progress = false;
-
- return StatusCodes.Good;
- }
-
- function _add_comment_method(inputArguments, context, callback) {
- //
- // The AddComment Method is used to apply a comment to a specific state of a Condition
- // instance. Normally, the NodeId of the object instance as the ObjectId is passed to the Call
- // Service. However, some Servers do not expose Condition instances in the AddressSpace.
- // Therefore all Servers shall also allow Clients to call the AddComment Method by specifying
- // ConditionId as the ObjectId. The Method cannot be called with an ObjectId of the
- // ConditionType Node.
- // Signature
- // - EventId EventId identifying a particular Event Notification where a state was reported for a
- // Condition.
- // - Comment A localized text to be applied to the Condition.
- //
- // AlwaysGeneratesEvent AuditConditionCommentEventType
- //
- UAConditionBase.with_condition_method(inputArguments, context, callback, function(
- eventId,
- comment,
- branch,
- conditionNode
- ) {
- assert(inputArguments instanceof Array);
- assert(eventId instanceof Buffer || eventId === null);
- assert(branch instanceof ConditionSnapshot);
- branch.setComment(comment);
-
- const sourceName = "Method/AddComment";
-
- conditionNode._raiseAuditConditionCommentEvent(sourceName, eventId, comment);
-
- // raise new event
- conditionNode.raiseConditionEvent(branch, true);
-
- /**
- * raised when the branch has been added a comment
- * @event addComment
- * @param eventId {Buffer|null}
- * @param comment {LocalizedText}
- * @param branch {ConditionSnapshot}
- */
- conditionNode.emit("addComment", eventId, comment, branch);
-
- return StatusCodes.Good;
- });
- }
-
- function _enable_method(inputArguments, context, callback) {
- assert(inputArguments.length === 0);
- const conditionNode = context.object;
- assert(conditionNode);
-
- if (!(conditionNode instanceof UAConditionBase)) {
- return callback(null, {
- statusCode: StatusCodes.BadNodeIdInvalid
- });
- }
- const statusCode = conditionNode._setEnabledState(true);
- return callback(null, {
- statusCode: statusCode
- });
- }
-
- function _disable_method(inputArguments, context, callback) {
- assert(inputArguments.length === 0);
-
- const conditionNode = context.object;
- assert(conditionNode);
-
- if (!(conditionNode instanceof UAConditionBase)) {
- return callback(null, {
- statusCode: StatusCodes.BadNodeIdInvalid
- });
- }
- const statusCode = conditionNode._setEnabledState(false);
- return callback(null, {
- statusCode: statusCode
- });
- }
-
- /**
- * verify that the subscription id belongs to the session that make the call.
- * @method _check_subscription_id_is_valid
- * @param subscriptionId {Number}
- * @param context {Object}
- * @private
- */
- function _check_subscription_id_is_valid(subscriptionId, context) {
- /// todo: return StatusCodes.BadSubscriptionIdInvalid; if subscriptionId doesn't belong to session...
- return StatusCodes.Good;
- }
-
- function _condition_refresh_method(inputArguments, context, callback) {
- // arguments : IntegerId SubscriptionId
- assert(inputArguments.length === 1);
-
- const addressSpace = context.object.addressSpace;
- if (doDebug) {
- debugLog(" ConditionType.ConditionRefresh ! subscriptionId =".red.bgWhite, inputArguments[0].toString());
- }
- const subscriptionId = inputArguments[0].value;
-
- let statusCode = _check_subscription_id_is_valid(subscriptionId, context);
- if (statusCode !== StatusCodes.Good) {
- return statusCode;
- }
-
- statusCode = _perform_condition_refresh(addressSpace, inputArguments, context);
- return callback(null, {
- statusCode: statusCode
- });
- }
-
- function _condition_refresh2_method(inputArguments, context, callback) {
- // arguments : IntegerId SubscriptionId
- // arguments : IntegerId MonitoredItemId
- assert(inputArguments.length === 2);
-
- const addressSpace = context.object.addressSpace;
-
- // istanbul ignore next
- if (doDebug) {
- debugLog(" ConditionType.conditionRefresh2 !".cyan.bgWhite);
- }
-
- //xx var subscriptionId = inputArguments[0].value;
- //xx var monitoredItemId = inputArguments[1].value;
-
- const statusCode = _perform_condition_refresh(addressSpace, inputArguments, context);
- return callback(null, {
- statusCode: statusCode
- });
- }
-
- UAConditionBase.install_condition_refresh_handle = function _install_condition_refresh_handle(addressSpace) {
- //
- // install CondititionRefresh
- //
- // NOTE:
- // OPCUA doesn't implement the condition refresh method ! yet
- // .5.7 ConditionRefresh Method
- // ConditionRefresh allows a Client to request a Refresh of all Condition instances that currently
- // are in an interesting state (they have the Retain flag set). This includes previous states of a
- // Condition instance for which the Server maintains Branches. A Client would typically invoke
- // this Method when it initially connects to a Server and following any situations, such as
- // communication disruptions, in which it would require resynchronization with the Server. This
- // Method is only available on the ConditionType or its subtypes. To invoke this Method, the call
- // shall pass the well known MethodId of the Method on the ConditionType and the ObjectId
- // shall be the well known ObjectId of the ConditionType Object.
-
- const conditionType = addressSpace.findEventType("ConditionType");
- assert(conditionType !== null);
-
- conditionType.disable.bindMethod(_disable_method);
- conditionType.enable.bindMethod(_enable_method);
-
- conditionType.conditionRefresh.bindMethod(_condition_refresh_method);
-
- conditionType.conditionRefresh2.bindMethod(_condition_refresh2_method);
-
- // those methods can be call on the ConditionType or on the ConditionInstance itself...
- conditionType.addComment.bindMethod(_add_comment_method);
- };
-
- /**
- * @method _getCompositeKey
- * @param node {BaseNode}
- * @param key {String}
- * @return {BaseNode}
- * @private
- *
- * @example
- *
- * var node = _getComposite(node,"enabledState.id");
- *
- */
- function _getCompositeKey(node, key) {
- let cur = node;
- const elements = key.split(".");
- for (let i = 0; i < elements.length; i++) {
- const e = elements[i];
-
- // istanbul ignore next
- if (!cur.hasOwnProperty(e)) {
- throw new Error(" cannot extract '" + key + "' from " + node.browseName.toString());
- }
-
- cur = cur[e];
- }
- return cur;
- }
-
- /**
- * instantiate a Condition.
- * this will create the unique EventId and will set eventType
- * @method instantiate
- * @param namespace {Namespace}
- * @param conditionTypeId {String|NodeId} the EventType to instantiate
- * @param options {object}
- * @param options.browseName {String|QualifiedName}
- * @param options.componentOf {NodeId|UAObject}
- * @param options.conditionOf {NodeId|UAObject} Mandatory
- * @param options.organizedBy {NodeId|UAObject} ( only provide componentOf or organizedBy but not both)
- * @param [options.conditionClass =BaseConditionClassType] {NodeId|UAObject}
- * The condition Class nodeId or object used to set the ConditionClassId and
- * ConditionClassName properties of the condition.
- *
- * @param options.conditionSource {NodeId|UAObject} the condition source node.
- * this node must be marked a EventSource.
- * the conditionSource is used to populate the sourceNode and
- * sourceName variables defined by BaseEventType
- * @param options.conditionName {String} the condition Name
- * @param [options.optionals] [Array<String>] an Array of optinals fields
- *
- * @param data {object} a object containing the value to set
- * @param data.eventId {String|NodeId} the EventType Identifier to instantiate (type cannot be abstract)
-
- * @return node {UAConditionBase}
- */
- UAConditionBase.instantiate = function(namespace, conditionTypeId, options, data) {
- /* eslint max-statements: ["error", 100] */
- assert(namespace instanceof Namespace);
- const addressSpace = namespace.addressSpace;
-
- const conditionType = addressSpace.findEventType(conditionTypeId);
-
- /* istanbul ignore next */
- if (!conditionType) {
- throw new Error(" cannot find Condition Type for " + conditionTypeId);
- }
-
- // reminder : abstract event type cannot be instantiated directly !
- assert(!conditionType.isAbstract);
-
- const baseConditionEventType = addressSpace.findEventType("ConditionType");
- /* istanbul ignore next */
- if (!baseConditionEventType) {
- throw new Error("cannot find ConditionType");
- }
-
- assert(conditionType.isSupertypeOf(baseConditionEventType));
-
- // assert(_.isString(options.browseName));
- options.browseName = options.browseName || "??? instantiateCondition - missing browseName";
-
- options.optionals = options.optionals || [];
-
- // now optionals in 1.04
- options.optionals.push("EventType");
- options.optionals.push("BranchId");
-
- //
- options.optionals.push("Comment");
- options.optionals.push("Comment.SourceTimestamp");
- options.optionals.push("EnabledState.TrueState");
- options.optionals.push("EnabledState.TrueState");
- options.optionals.push("EnabledState.FalseState");
-
- options.optionals.push("EnabledState.TransitionTime");
- options.optionals.push("EnabledState.EffectiveTransitionTime");
- options.optionals.push("EnabledState.EffectiveDisplayName");
-
- const conditionNode = conditionType.instantiate(options);
- Object.setPrototypeOf(conditionNode, UAConditionBase.prototype);
- conditionNode.initialize();
-
- assert(
- options.hasOwnProperty("conditionSource"),
- "must specify a condition source either as null or as a UAObject"
- );
- if (!options.conditionOf) {
- options.conditionOf = options.conditionSource;
- }
- if (options.conditionOf) {
- assert(options.hasOwnProperty("conditionOf")); // must provide a conditionOf
- options.conditionOf = addressSpace._coerceNode(options.conditionOf);
-
- // HasCondition References can be used in the Type definition of an Object or a Variable.
- assert(options.conditionOf instanceof UAObject || options.conditionOf instanceof UAVariable);
-
- conditionNode.addReference({
- referenceType: "HasCondition",
- isForward: false,
- nodeId: options.conditionOf
- });
- assert(conditionNode.conditionOfNode().nodeId === options.conditionOf.nodeId);
- }
-
- /**
- * dataType is DataType.NodeId
- * @property eventType
- * @type {UAVariableType}
- *
- */
- // the constant property of this condition
- conditionNode.eventType.setValueFromSource({
- dataType: DataType.NodeId,
- value: conditionType.nodeId
- });
-
- data = data || {};
- // install initial branch ID (null NodeId);
- /**
- * dataType is DataType.NodeId
- * @property branchId
- * @type {UAVariableType}
- *
- */
- conditionNode.branchId.setValueFromSource({
- dataType: DataType.NodeId,
- value: NodeId.NullNodeId
- });
-
- // install 'Comment' condition variable
- /**
- * dataType is DataType.LocalizedText
- * @property comment
- * @type {UAVariableType}
- *
- */
- _install_condition_variable_type(conditionNode.comment);
-
- // install 'Quality' condition variable
- /**
- * dataType is DataType.StatusCode
- * @property quality
- * @type {UAVariableType}
- *
- */
- _install_condition_variable_type(conditionNode.quality);
- //xx conditionNode.quality.setValueFromSource({dataType: DataType.StatusCode,value: StatusCodes.Good });
-
- // install 'LastSeverity' condition variable
- /**
- * dataType is DataType.StatusCode
- * @property lastSeverity
- * @type {UAVariableType}
- *
- */
- _install_condition_variable_type(conditionNode.lastSeverity);
- //xx conditionNode.severity.setValueFromSource({dataType: DataType.UInt16,value: 0 });
- //xx conditionNode.lastSeverity.setValueFromSource({dataType: DataType.UInt16,value: 0 });
-
- // install 'EnabledState' TwoStateVariable
- /**
- * @property enabledState
- * @type {UATwoStateVariable}
- */
- // -------------- fixing missing EnabledState.EffectiveDisplayName
- if (!conditionNode.enabledState.effectiveDisplayName) {
- namespace.addVariable({
- browseName: "EffectiveDisplayName",
- dataType: "LocalizedText",
- propertyOf: conditionNode.enabledState
- });
- }
- AddressSpace._install_TwoStateVariable_machinery(conditionNode.enabledState, {
- trueState: "Enabled",
- falseState: "Disabled"
- });
- assert(conditionNode.enabledState._trueState === "Enabled");
- assert(conditionNode.enabledState._falseState === "Disabled");
-
- // installing sourceName and sourceNode
- conditionNode.enabledState.setValue(true);
-
- // set properties to in initial values
- Object.keys(data).forEach(function(key) {
- const varNode = _getCompositeKey(conditionNode, key);
- assert(varNode instanceof UAVariable);
-
- const variant = new Variant(data[key]);
-
- // check that Variant DataType is compatible with the UAVariable dataType
- //xx var nodeDataType = addressSpace.findNode(varNode.dataType).browseName;
-
- /* istanbul ignore next */
- if (!varNode._validate_DataType(variant.dataType)) {
- throw new Error(" Invalid variant dataType " + variant + " " + varNode.browseName.toString());
- }
-
- const value = new Variant(data[key]);
-
- varNode.setValueFromSource(value);
- });
-
- // bind condition methods -
- /**
- * @property enable
- * @type {UAMethod}
- */
- conditionNode.enable.bindMethod(_enable_method);
-
- /**
- * @property disable
- * @type {UAMethod}
- */
- conditionNode.disable.bindMethod(_disable_method);
-
- // bind condition methods - AddComment
- /**
- * @property addComment
- * @type {UAMethod}
- */
- conditionNode.addComment.bindMethod(_add_comment_method);
-
- assert(conditionNode instanceof UAConditionBase);
-
- // ConditionSource => cf SourceNode
- // As per spec OPCUA 1.03 part 9 page 54:
- // The ConditionType inherits all Properties of the BaseEventType. Their semantic is defined in
- // Part 5. SourceNode identifies the ConditionSource.
- // The SourceNode is the Node which the condition is associated with, it may be the same as the
- // InputNode for an alarm, but it may be a separate node. For example a motor, which is a
- // variable with a value that is an RPM, may be the ConditionSource for Conditions that are
- // related to the motor as well as a temperature sensor associated with the motor. In the former
- // the InputNode for the High RPM alarm is the value of the Motor RPM, while in the later the
- // InputNode of the High Alarm would be the value of the temperature sensor that is associated
- // with the motor.
- /**
- * dataType is DataType.NodeId
- * @property sourceNode
- * @type {UAVariableType}
- *
- */
- if (options.conditionSource !== null) {
- options.conditionSource = addressSpace._coerceNode(options.conditionSource);
- assert(options.conditionSource instanceof BaseNode);
-
- const conditionSourceNode = addressSpace.findNode(options.conditionSource.nodeId);
-
- conditionNode.sourceNode.setValueFromSource({
- dataType: DataType.NodeId,
- value: conditionSourceNode.nodeId
- });
-
- // conditionSourceNode node must be registered as a EventSource of an other node.
- // As per spec OPCUA 1.03 part 9 page 54:
- // HasNotifier and HasEventSource References are used to expose the hierarchical organization
- // of Event notifying Objects and ConditionSources. An Event notifying Object represents
- // typically an area of Operator responsibility. The definition of such an area configuration is
- // outside the scope of this standard. If areas are available they shall be linked together and
- // with the included ConditionSources using the HasNotifier and the HasEventSource Reference
- // Types. The Server Object shall be the root of this hierarchy.
- assert(conditionSourceNode.getEventSourceOfs().length >= 1, "conditionSourceNode must be an event source");
-
- const context = SessionContext.defaultContext;
- // set source Node (defined in UABaseEventType)
- conditionNode.sourceNode.setValueFromSource(
- conditionSourceNode.readAttribute(context, AttributeIds.NodeId).value
- );
-
- // set source Name (defined in UABaseEventType)
- conditionNode.sourceName.setValueFromSource(
- conditionSourceNode.readAttribute(context, AttributeIds.DisplayName).value
- );
- }
-
- conditionNode.eventType.setValueFromSource({
- dataType: DataType.NodeId,
- value: conditionType.nodeId
- });
- // as per spec:
-
- /**
- *
- * dataType: DataType.NodeId
- *
- * As per spec OPCUA 1.03 part 9:
- * ConditionClassId specifies in which domain this Condition is used. It is the NodeId of the
- * corresponding ConditionClassType. See 5.9 for the definition of ConditionClass and a set of
- * ConditionClasses defined in this standard. When using this Property for filtering, Clients have
- * to specify all individual ConditionClassType NodeIds. The OfType operator cannot be applied.
- * BaseConditionClassType is used as class whenever a Condition cannot be assigned to a
- * more concrete class.
- *
- * BaseConditionClassType
- * |
- * +---------------------------+----------------------------+
- * | | |
- * ProcessConditionClassType MaintenanceConditionClassType SystemConditionClassType
- *
- * @property conditionName
- * @type {UAVariable}
- */
- const baseConditionClassType = addressSpace.findObjectType("ProcessConditionClassType");
- //assert(baseConditionClassType,"Expecting BaseConditionClassType to be in addressSpace");
- let conditionClassId = baseConditionClassType ? baseConditionClassType.nodeId : NodeId.NullNodeId;
- let conditionClassName = baseConditionClassType ? baseConditionClassType.displayName[0] : "";
- if (options.conditionClass) {
- if (_.isString(options.conditionClass)) {
- options.conditionClass = addressSpace.findObjectType(options.conditionClass);
- }
- const conditionClassNode = addressSpace._coerceNode(options.conditionClass);
- if (!conditionClassNode) {
- throw new Error("cannot find condition class " + options.conditionClass.toString());
- }
- conditionClassId = conditionClassNode.nodeId;
- conditionClassName = conditionClassNode.displayName[0];
- }
- conditionNode.conditionClassId.setValueFromSource({
- dataType: DataType.NodeId,
- value: conditionClassId
- });
-
- // as per spec:
- // ConditionClassName provides the display name of the ConditionClassType.
- conditionNode.conditionClassName.setValueFromSource({
- dataType: DataType.LocalizedText,
- value: coerceLocalizedText(conditionClassName)
- });
-
- // as per spec:
- /**
- *
- * dataType: DataType.String
- *
- * As per spec OPCUA 1.03 part 9:
- * ConditionName identifies the Condition instance that the Event originated from. It can be used
- * together with the SourceName in a user display to distinguish between different Condition
- * instances. If a ConditionSource has only one instance of a ConditionType, and the Server has
- * no instance name, the Server shall supply the ConditionType browse name.
- * @property conditionName
- * @type {UAVariable}
- */
- const conditionName = options.conditionName || "Unset Condition Name";
- assert(_.isString(conditionName));
- conditionNode.conditionName.setValueFromSource({
- dataType: DataType.String,
- value: conditionName
- });
-
- // set SourceNode and SourceName based on HasCondition node
- const sourceNodes = conditionNode.findReferencesAsObject("HasCondition", false);
- if (sourceNodes.length) {
- assert(sourceNodes.length === 1);
- conditionNode.setSourceNode(sourceNodes[0].nodeId);
- conditionNode.setSourceName(sourceNodes[0].browseName.toString());
- }
-
- conditionNode.post_initialize();
-
- const branch0 = conditionNode.currentBranch();
- branch0.setRetain(false);
- branch0.setComment("Initialized");
- branch0.setQuality(StatusCodes.Good);
- branch0.setSeverity(0);
- branch0.setLocalTime(
- new TimeZone({
- offset: 0,
- daylightSavingInOffset: false
- })
- );
- branch0.setMessage(" ");
-
- branch0.setReceiveTime(minDate);
- branch0.setTime(minDate);
-
- // UAConditionBase
- return conditionNode;
- };
-
- /*
- As per spec OPCUA 1.03 part 9:
-
- A Condition’s EnabledState effects the generation of Event Notifications and as such results
- in the following specific behaviour:
- * When the Condition instance enters the Disabled state, the Retain Property of this
- Condition shall be set to FALSE by the Server to indicate to the Client that the
- Condition instance is currently not of interest to Clients.
- * When the Condition instance enters the enabled state, the Condition shall be
- evaluated and all of its Properties updated to reflect the current values. If this
- evaluation causes the Retain Property to transition to TRUE for any ConditionBranch,
- then an Event Notification shall be generated for that ConditionBranch.
- * The Server may choose to continue to test for a Condition instance while it is
- Disabled. However, no Event Notifications will be generated while the Condition
- instance is disabled.
- * For any Condition that exists in the AddressSpace the Attributes and the following
- Variables will continue to have valid values even in the Disabled state; EventId, Event
- Type, Source Node, Source Name, Time, and EnabledState.
- Other properties may no longer provide current valid values.
- All Variables that are no longer provided shall return a status of Bad_ConditionDisabled.
- The Event that reports the Disabled state should report the properties as NULL or with a status
- of Bad_ConditionDisabled.
- When enabled, changes to the following components shall cause a ConditionType Event Notification:
- - Quality
- - Severity (inherited from BaseEventType)
- - Comment
-
- // spec :
- // The HasCondition ReferenceType is a concrete ReferenceType and can be used directly. It is
- // a subtype of NonHierarchicalReferences.
- // The semantic of this ReferenceType is to specify the relationship between a ConditionSource
- // and its Conditions. Each ConditionSource shall be the target of a HasEventSource Reference
- // or a sub type of HasEventSource. The AddressSpace organisation that shall be provided for
- // Clients to detect Conditions and ConditionSources is defined in Clause 6. Various examples
- // for the use of this ReferenceType can be found in B.2.
- // HasCondition References can be used in the Type definition of an Object or a Variable. In this
- // case, the SourceNode of this ReferenceType shall be an ObjectType or VariableType Node or
- // one of their InstanceDeclaration Nodes. The TargetNode shall be a Condition instance
- // declaration or a ConditionType. The following rules for instantiation apply:
- // All HasCondition References used in a Type shall exist in instances of these Types as
- // well.
- // If the TargetNode in the Type definition is a ConditionType, the same TargetNode will
- // be referenced on the instance.
- // HasCondition References may be used solely in the instance space when they are not
- // available in Type definitions. In this case the SourceNode of this ReferenceType shall be an
- // Object, Variable or Method Node. The TargetNode shall be a Condition instance or a
- // ConditionType.
-
- */
-
-