/** * @module node-opcua-address-space.AlarmsAndConditions */ import type { INamespace, UAProperty, UAVariable, UAVariableT } from "node-opcua-address-space-base"; import { assert } from "node-opcua-assert"; import type { DataValue } from "node-opcua-data-value"; import { NodeId, type NodeIdLike } from "node-opcua-nodeid"; import type { UAOffNormalAlarm_Base } from "node-opcua-nodeset-ua"; import { isNullOrUndefined } from "node-opcua-utils"; import { DataType, type VariantOptions } from "node-opcua-variant"; import type { InstantiateOffNormalAlarmOptions } from "../../api/interfaces/alarms_and_conditions/instantiate_off_normal_alarm_options.js"; import type { UADiscreteAlarmEx } from "../../api/interfaces/alarms_and_conditions/ua_discrete_alarm_ex.js"; import type { AddressSpacePrivate } from "../address_space_private.js"; import { UADiscreteAlarmImplBase } from "./ua_discrete_alarm_impl.js"; function isEqual(value1: unknown, value2: unknown): boolean { return value1 === value2; } export declare interface UAOffNormalAlarmEx extends Omit< UAOffNormalAlarm_Base, | "ackedState" | "activeState" | "confirmedState" | "enabledState" | "latchedState" | "outOfServiceState" | "shelvingState" | "silenceState" | "suppressedState" >, UADiscreteAlarmEx { getNormalStateNode(): UAVariableT | null; getNormalStateValue(): NodeId | null; setNormalStateValue(value: NodeIdLike): void; } /** * The OffNormalAlarmType is a specialization of the DiscreteAlarmType intended to represent a * discrete Condition that is considered to be not normal. * This sub type is usually used to indicate that a discrete value is in an Alarm state, it is active as * long as a non-normal value is present. */ export class UAOffNormalAlarmImplBase extends UADiscreteAlarmImplBase implements UAOffNormalAlarmEx { /** installed as a child node by the address space, not assigned here - hence `declare` */ declare public readonly normalState: UAProperty; /** * When the value of inputNode doesn't match the normalState node value, then the alarm is raised. * */ public static instantiate<_T, _DT extends DataType>( namespace: INamespace, limitAlarmTypeId: string | NodeId, options: InstantiateOffNormalAlarmOptions, data?: Record ): UAOffNormalAlarmImpl { const addressSpace = namespace.addressSpace as AddressSpacePrivate; const offNormalAlarmType = addressSpace.findEventType("OffNormalAlarmType"); /* c8 ignore next */ if (!offNormalAlarmType) { throw new Error("cannot find offNormalAlarmType"); } assert(Object.hasOwn(options, "inputNode"), "must provide inputNode"); // must provide a inputNode assert(Object.hasOwn(options, "normalState"), "must provide a normalState Node"); // must provide a inputNode options.optionals = options.optionals || []; assert(Object.hasOwn(options, "inputNode"), "must provide inputNode"); // must provide a inputNode const alarmNode = UADiscreteAlarmImplBase.instantiate(namespace, limitAlarmTypeId, options, data) as UAOffNormalAlarmImpl; Object.setPrototypeOf(alarmNode, UAOffNormalAlarmImpl.prototype); /** * 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. */ const inputNode = addressSpace._coerceNode(options.inputNode) as UAVariable | null; // note: alarmNode.inputNode.readValue() already set by DiscreteAlarmImpl.instantiate if (inputNode) { // install inputNode Node monitoring for change alarmNode.installInputNodeMonitoring(options.inputNode); } /** * The NormalState Property is a Property that points to a Variable which has a value that corresponds to one * of the possible values of the Variable pointed to by the InputNode Property where the NormalState Property * Variable value is the value that is considered to be the normal state of the Variable pointed to by the InputNode * Property. When the value of the Variable referenced by the InputNode Property is not equal to the value of the * NormalState Property the Alarm is Active. * * If this Variable is not in the AddressSpace, a NULL NodeId shall be provided. * */ if (options.normalState) { const normalState = addressSpace._coerceNode(options.normalState) as UAVariable | null; const normalStateNodeId = normalState ? normalState.nodeId : new NodeId(); alarmNode.normalState.setValueFromSource({ dataType: DataType.NodeId, value: normalStateNodeId }); alarmNode.normalState.on("value_changed", (_newDataValue: DataValue) => { // The node that contains the normalState value has changed. // we must remove the listener on current normalState and replace // normalState with the new one and set listener again // to do: }); if (normalState) { // install normalState monitoring for change normalState.on("value_changed", (newDataValue: DataValue /*, oldDataValue: DataValue*/) => { alarmNode._onNormalStateDataValueChange(newDataValue); }); } } else { alarmNode.normalState.setValueFromSource({ dataType: DataType.NodeId, value: NodeId.nullNodeId }); } alarmNode._mayBe_updateAlarmState(); return alarmNode; } // HasProperty Variable NormalState NodeId PropertyType Mandatory // The NormalState Property is a Property that points to a Variable which has a value that // corresponds to one of the possible values of the Variable pointed to by the InputNode // Property where the NormalState Property Variable value is the value that is considered to be // the normal state of the Variable pointed to by the InputNode Property. When the value of the // Variable referenced by the InputNode Property is not equal to the value of the NormalState // Property the Alarm is Active. If this Variable is not in the AddressSpace, a Null NodeId shall // be provided. public getNormalStateNode(): UAVariableT | null { const nodeId = this.normalState.readValue().value.value; const node = this.addressSpace.findNode(nodeId) as UAVariableT; if (!node) { return null; } return node; } /** */ public getNormalStateValue(): NodeId | null { const normalStateNode = this.getNormalStateNode(); if (!normalStateNode) { return null; } return normalStateNode.readValue().value.value as NodeId; } /** */ public setNormalStateValue(_value: NodeIdLike): void { const _normalStateNode = this.getNormalStateNode(); throw new Error("Not Implemented yet"); } public updateAlarmState(isActive: boolean, message: string) { if (isActive === this.activeState.getValue()) { // no change => ignore ! return; } const stateName = isActive ? "Active" : "Inactive"; // also raise the event this._signalNewCondition(stateName, isActive, message); if (!isActive) { this.currentBranch().setRetain(false); } } _mayBe_updateAlarmState(normalStateValue?: NodeId | null, inputValue?: unknown): void { if (isNullOrUndefined(normalStateValue) || isNullOrUndefined(inputValue)) { this.activeState.setValue(false); return; } const isActive = !isEqual(normalStateValue, inputValue); this.updateAlarmState(isActive, "automatique update"); } protected _onInputDataValueChange(dataValue: DataValue): void { if (dataValue.statusCode.isNotGood()) { // what shall we do ? return; } if (dataValue.value.dataType === DataType.Null) { // what shall we do ? return; } const inputValue = dataValue.value.value; const normalStateValue = this.getNormalStateValue(); this._mayBe_updateAlarmState(normalStateValue, inputValue); } protected _onNormalStateDataValueChange(dataValue: DataValue): void { if (dataValue.statusCode.isNotGood()) { // what shall we do ? return; } if (dataValue.value.dataType === DataType.Null) { // what shall we do ? return; } const normalStateValue = dataValue.value.value; const inputValue = this.getInputNodeValue(); this._mayBe_updateAlarmState(normalStateValue, inputValue); } } export type UAOffNormalAlarmImpl = UAOffNormalAlarmImplBase; export const UAOffNormalAlarmImpl = UAOffNormalAlarmImplBase;