"use strict";
/**
* @module opcua.address_space.AlarmsAndConditions
*/
const util = require("util");
const assert = require("node-opcua-assert").assert;
const _ = require("underscore");
const StatusCodes = require("node-opcua-status-code").StatusCodes;
const DataType = require("node-opcua-variant").DataType;
const UAAlarmConditionBase = require("./alarm_condition").UAAlarmConditionBase;
const UAVariable = require("../ua_variable").UAVariable;
const ConditionInfo = require("./condition").ConditionInfo;
const DataValue = require("node-opcua-data-value").DataValue;
const NodeId = require("node-opcua-nodeid").NodeId;
const AddressSpace =require("../address_space").AddressSpace;
const Namespace = require("../namespace").Namespace;
/**
* @class UALimitAlarm
* @constructor
* @extends UAAlarmConditionBase
*/
function UALimitAlarm() {
/**
* @property activeState
* @type UATwoStateVariable
*/
}
util.inherits(UALimitAlarm, UAAlarmConditionBase);
/**
* @method getHighHighLimit
* @return {Number}
*/
UALimitAlarm.prototype.getHighHighLimit = function () {
return this.highHighLimit.readValue().value.value;
};
/**
* @method getHighLimit
* @return {Number}
*/
UALimitAlarm.prototype.getHighLimit = function () {
return this.highLimit.readValue().value.value;
};
/**
* @method getLowLimit
* @return {Float}
*/
UALimitAlarm.prototype.getLowLimit = function () {
return this.lowLimit.readValue().value.value;
};
/**
* @method getLowLowLimit
* @return {Float}
*/
UALimitAlarm.prototype.getLowLowLimit = function () {
return this.lowLowLimit.readValue().value.value;
};
/**
* @method setHighHighLimit
* @param value {Float}
*/
UALimitAlarm.prototype.setHighHighLimit = function (value) {
assert(this.highHighLimit, "LimitAlarm instance must expose the optional HighHighLimit property");
this.highHighLimit.setValueFromSource({ dataType: this._dataType, value: value });
};
/**
* @method setHighLimit
* @param value {Float}
*/
UALimitAlarm.prototype.setHighLimit = function (value) {
assert(this.highLimit, "LimitAlarm instance must expose the optional HighLimit property");
this.highLimit.setValueFromSource({ dataType: this._dataType, value: value });
};
/**
* @method setLowLimit
* @param value {Float}
*/
UALimitAlarm.prototype.setLowLimit = function (value) {
assert(this.lowLimit, "LimitAlarm instance must expose the optional LowLimit property");
this.lowLimit.setValueFromSource({ dataType: this._dataType, value: value });
};
/**
* @method setLowLowLimit
* @param value {Float}
*/
UALimitAlarm.prototype.setLowLowLimit = function (value) {
assert(this.lowLowLimit, "LimitAlarm instance must expose the optional LowLowLimit property");
this.lowLowLimit.setValueFromSource({ dataType: this._dataType, value: value });
};
UALimitAlarm.prototype._onInputDataValueChange = function (dataValue) {
assert(dataValue instanceof DataValue);
const alarm = this;
if (dataValue.statusCode === StatusCodes.BadWaitingForInitialData) {
// we are not ready yet to use the input node value
return;
}
if (dataValue.statusCode !== StatusCodes.Good) {
// what shall we do ?
alarm._signalNewCondition(null);
return;
}
if (dataValue.value.dataType === DataType.Null) {
// what shall we do ?
alarm._signalNewCondition(null);
return;
}
const value = dataValue.value.value;
alarm._setStateBasedOnInputValue(value);
};
UALimitAlarm.prototype._watchLimits = function() {
const alarm = this;
/// ----------------------------------------------------------------------
/// Installing Limits monitored
function _updateState() { alarm.updateState(); }
if (alarm.highHighLimit) {alarm.highHighLimit.on("value_changed",_updateState);}
if (alarm.highLimit) {alarm.highLimit.on("value_changed",_updateState);}
if (alarm.lowLimit) {alarm.lowLimit.on("value_changed",_updateState);}
if (alarm.lowLowLimit) {alarm.lowLowLimit.on("value_changed",_updateState);}
};
exports.UALimitAlarm = UALimitAlarm;
/**
* @method (static)UALimitAlarm.instantiate
* @param namespace {Namespace}
* @param limitAlarmTypeId
* @param options
* @param options.inputNode
* @param options.optionals
* @param options.highHighLimit {Double}
* @param options.highLimit {Double}
* @param options.lowLimit {Double}
* @param options.lowLowLimit {Double}
* @param data
* @return {UALimitAlarm}
*/
UALimitAlarm.instantiate = function (namespace, limitAlarmTypeId, options, data) {
assert(namespace instanceof Namespace);
const addressSpace = namespace.addressSpace;
assert(addressSpace instanceof AddressSpace);
/* eslint max-instructions: off */
// must provide a inputNode
//xx assert(options.hasOwnProperty("conditionOf")); // must provide a conditionOf
assert(options.hasOwnProperty("inputNode"), "UALimitAlarm.instantiate: options must provide the inputNode");
options.optionals = options.optionals || [];
let count = 0;
if (options.hasOwnProperty("highHighLimit")) {
options.optionals.push("HighHighLimit");
options.optionals.push("HighHighState");
count++;
}
if (options.hasOwnProperty("highLimit")) {
options.optionals.push("HighLimit");
options.optionals.push("HighState");
count++;
}
if (options.hasOwnProperty("lowLimit")) {
options.optionals.push("LowLimit");
options.optionals.push("LowState");
count++;
}
if (options.hasOwnProperty("lowLowLimit")) {
options.optionals.push("LowLowLimit");
options.optionals.push("LowLowState");
count++;
}
//xx assert(options.optionals,"must provide an optionals");
const alarmNode = UAAlarmConditionBase.instantiate(namespace, limitAlarmTypeId, options, data);
Object.setPrototypeOf(alarmNode, UALimitAlarm.prototype);
assert(alarmNode.conditionOfNode() !== null);
const inputNode = addressSpace._coerceNode(options.inputNode);
assert(inputNode, "Expecting a valid input node");
assert(inputNode instanceof UAVariable);
// ----------------------- Install Limit Alarm specifics
// from spec 1.03:
// Four optional limits are defined that configure the states of the derived limit Alarm Types.
// These Properties shall be set for any Alarm limits that are exposed by the derived limit Alarm
// Types. These Properties are listed as optional but at least one is required. For cases where
// an underlying system cannot provide the actual value of a limit, the limit Property shall still be
// provided, but will have its AccessLevel set to not readable. It is assumed that the limits are
// described using the same Engineering Unit that is assigned to the variable that is the source
// of the alarm. For Rate of change limit alarms, it is assumed this rate is units per second
// unless otherwise specified.
if (count === 0) {
throw new Error("at least one limit is required");
}
const dataType = addressSpace.findCorrespondingBasicDataType(options.inputNode.dataType);
alarmNode._dataType = dataType;
if (options.hasOwnProperty("highHighLimit")) {
alarmNode.setHighHighLimit(options.highHighLimit);
}
if (options.hasOwnProperty("highLimit")) {
alarmNode.setHighLimit(options.highLimit);
}
if (options.hasOwnProperty("lowLimit")) {
alarmNode.setLowLimit(options.lowLimit);
}
if (options.hasOwnProperty("lowLowLimit")) {
alarmNode.setLowLowLimit(options.lowLowLimit);
}
/*
* The InputNode Property provides the NodeId of the Variable the Value of which is used as
* primary input in the calculation of the Alarm state. If this Variable is not in the AddressSpace,
* a Null NodeId shall be provided. In some systems, an Alarm may be calculated based on
* multiple Variables Values; it is up to the system to determine which Variable’s NodeId is used.
*/
assert(alarmNode.inputNode instanceof UAVariable);
alarmNode.inputNode.setValueFromSource({ dataType: "NodeId", value: inputNode.nodeId });
// install inputNode monitoring for change
alarmNode._installInputNodeMonitoring(options.inputNode);
alarmNode._watchLimits();
return alarmNode;
};
UALimitAlarm.prototype.evaluateConditionsAfterEnabled = function () {
assert(this.getEnabledState() === true);
//simulate input value event
const alarmNode = this;
const dataValue = alarmNode.getInputNodeNode().readValue();
alarmNode._onInputDataValueChange(dataValue);
};