node-opcua-address-space 2.182.2 → 2.183.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/api/helpers/argument_list.ts +8 -9
  2. package/api/helpers/call_helpers.ts +2 -0
  3. package/api/index.ts +14 -0
  4. package/api/interfaces/alarms_and_conditions/deviation_stuff.ts +30 -0
  5. package/api/interfaces/alarms_and_conditions/promote_to_alarm_options.ts +24 -0
  6. package/api/interfaces/alarms_and_conditions/ua_alarm_condition_ex.ts +33 -0
  7. package/api/interfaces/alarms_and_conditions/ua_limit_alarm_ex.ts +18 -0
  8. package/api/loader/nodeset_record.ts +2 -2
  9. package/api/loader/nodeset_record_applier.ts +1 -0
  10. package/api/loader/nodeset_xml_producer.ts +5 -1
  11. package/api/loader/register_node_promoter.ts +10 -2
  12. package/api/namespace_of.ts +28 -0
  13. package/dist/api/helpers/argument_list.js +8 -7
  14. package/dist/api/helpers/argument_list.js.map +1 -1
  15. package/dist/api/helpers/call_helpers.js +2 -0
  16. package/dist/api/helpers/call_helpers.js.map +1 -1
  17. package/dist/api/index.d.ts +11 -0
  18. package/dist/api/index.js +16 -3
  19. package/dist/api/index.js.map +1 -1
  20. package/dist/api/interfaces/alarms_and_conditions/deviation_stuff.d.ts +28 -0
  21. package/dist/api/interfaces/alarms_and_conditions/promote_to_alarm_options.d.ts +23 -0
  22. package/dist/api/interfaces/alarms_and_conditions/promote_to_alarm_options.js +3 -0
  23. package/dist/api/interfaces/alarms_and_conditions/promote_to_alarm_options.js.map +1 -0
  24. package/dist/api/interfaces/alarms_and_conditions/ua_alarm_condition_ex.d.ts +31 -0
  25. package/dist/api/interfaces/alarms_and_conditions/ua_limit_alarm_ex.d.ts +17 -0
  26. package/dist/api/loader/nodeset_record.d.ts +1 -1
  27. package/dist/api/loader/nodeset_record.js +1 -1
  28. package/dist/api/loader/nodeset_record_applier.js +1 -0
  29. package/dist/api/loader/nodeset_record_applier.js.map +1 -1
  30. package/dist/api/loader/nodeset_xml_producer.js +7 -1
  31. package/dist/api/loader/nodeset_xml_producer.js.map +1 -1
  32. package/dist/api/loader/register_node_promoter.d.ts +9 -1
  33. package/dist/api/loader/register_node_promoter.js +9 -2
  34. package/dist/api/loader/register_node_promoter.js.map +1 -1
  35. package/dist/api/namespace_of.d.ts +23 -0
  36. package/dist/api/namespace_of.js +29 -0
  37. package/dist/api/namespace_of.js.map +1 -0
  38. package/dist/impl/address_space_change_event_tools.js +4 -1
  39. package/dist/impl/address_space_change_event_tools.js.map +1 -1
  40. package/dist/impl/address_space_private.d.ts +6 -6
  41. package/dist/impl/alarms_and_conditions/deviation_alarm_helper.js +3 -0
  42. package/dist/impl/alarms_and_conditions/deviation_alarm_helper.js.map +1 -1
  43. package/dist/impl/alarms_and_conditions/ua_acknowledgeable_condition_impl.d.ts +7 -0
  44. package/dist/impl/alarms_and_conditions/ua_acknowledgeable_condition_impl.js +47 -37
  45. package/dist/impl/alarms_and_conditions/ua_acknowledgeable_condition_impl.js.map +1 -1
  46. package/dist/impl/alarms_and_conditions/ua_alarm_condition_impl.d.ts +72 -2
  47. package/dist/impl/alarms_and_conditions/ua_alarm_condition_impl.js +220 -101
  48. package/dist/impl/alarms_and_conditions/ua_alarm_condition_impl.js.map +1 -1
  49. package/dist/impl/alarms_and_conditions/ua_condition_impl.d.ts +19 -0
  50. package/dist/impl/alarms_and_conditions/ua_condition_impl.js +71 -16
  51. package/dist/impl/alarms_and_conditions/ua_condition_impl.js.map +1 -1
  52. package/dist/impl/alarms_and_conditions/ua_exclusive_deviation_alarm_impl.d.ts +9 -1
  53. package/dist/impl/alarms_and_conditions/ua_exclusive_deviation_alarm_impl.js +13 -3
  54. package/dist/impl/alarms_and_conditions/ua_exclusive_deviation_alarm_impl.js.map +1 -1
  55. package/dist/impl/alarms_and_conditions/ua_exclusive_limit_alarm_impl.d.ts +2 -2
  56. package/dist/impl/alarms_and_conditions/ua_exclusive_limit_alarm_impl.js +4 -4
  57. package/dist/impl/alarms_and_conditions/ua_exclusive_limit_alarm_impl.js.map +1 -1
  58. package/dist/impl/alarms_and_conditions/ua_limit_alarm_impl.d.ts +13 -2
  59. package/dist/impl/alarms_and_conditions/ua_limit_alarm_impl.js +21 -5
  60. package/dist/impl/alarms_and_conditions/ua_limit_alarm_impl.js.map +1 -1
  61. package/dist/impl/alarms_and_conditions/ua_non_exclusive_deviation_alarm_impl.d.ts +9 -1
  62. package/dist/impl/alarms_and_conditions/ua_non_exclusive_deviation_alarm_impl.js +13 -3
  63. package/dist/impl/alarms_and_conditions/ua_non_exclusive_deviation_alarm_impl.js.map +1 -1
  64. package/dist/impl/alarms_and_conditions/ua_non_exclusive_limit_alarm_impl.d.ts +1 -1
  65. package/dist/impl/alarms_and_conditions/ua_non_exclusive_limit_alarm_impl.js +2 -2
  66. package/dist/impl/alarms_and_conditions/ua_non_exclusive_limit_alarm_impl.js.map +1 -1
  67. package/dist/impl/alarms_and_conditions/ua_off_normal_alarm_impl.d.ts +1 -1
  68. package/dist/impl/alarms_and_conditions/ua_off_normal_alarm_impl.js +2 -2
  69. package/dist/impl/alarms_and_conditions/ua_off_normal_alarm_impl.js.map +1 -1
  70. package/dist/impl/base_node_impl.d.ts +2 -0
  71. package/dist/impl/base_node_impl.js +28 -0
  72. package/dist/impl/base_node_impl.js.map +1 -1
  73. package/dist/impl/base_node_private.d.ts +2 -0
  74. package/dist/impl/base_node_private.js.map +1 -1
  75. package/dist/impl/data_access/adjust_datavalue_status_code.d.ts +2 -2
  76. package/dist/impl/data_access/adjust_datavalue_status_code.js +35 -4
  77. package/dist/impl/data_access/adjust_datavalue_status_code.js.map +1 -1
  78. package/dist/impl/data_access/semantics_changed.d.ts +31 -0
  79. package/dist/impl/data_access/semantics_changed.js +77 -0
  80. package/dist/impl/data_access/semantics_changed.js.map +1 -0
  81. package/dist/impl/data_access/ua_multistate_discrete_impl.js +0 -2
  82. package/dist/impl/data_access/ua_multistate_discrete_impl.js.map +1 -1
  83. package/dist/impl/data_access/ua_two_state_discrete_impl.js +6 -15
  84. package/dist/impl/data_access/ua_two_state_discrete_impl.js.map +1 -1
  85. package/dist/impl/historical_access/read_processed_details_hook.d.ts +22 -0
  86. package/dist/impl/historical_access/read_processed_details_hook.js +12 -0
  87. package/dist/impl/historical_access/read_processed_details_hook.js.map +1 -0
  88. package/dist/impl/namespace_impl.js +2 -4
  89. package/dist/impl/namespace_impl.js.map +1 -1
  90. package/dist/impl/nodeid_manager.d.ts +5 -0
  91. package/dist/impl/nodeid_manager.js +21 -5
  92. package/dist/impl/nodeid_manager.js.map +1 -1
  93. package/dist/impl/ua_object_impl.js +13 -1
  94. package/dist/impl/ua_object_impl.js.map +1 -1
  95. package/dist/impl/ua_object_type_impl.js +1 -0
  96. package/dist/impl/ua_object_type_impl.js.map +1 -1
  97. package/dist/impl/ua_variable_impl.js +12 -1
  98. package/dist/impl/ua_variable_impl.js.map +1 -1
  99. package/dist/impl/ua_variable_type_impl.js +1 -0
  100. package/dist/impl/ua_variable_type_impl.js.map +1 -1
  101. package/dist/tsconfig_base.tsbuildinfo +1 -1
  102. package/distHelpers/get_address_space_fixture.js +2 -1
  103. package/distHelpers/get_address_space_fixture.js.map +1 -1
  104. package/distHelpers/index.d.ts +1 -0
  105. package/distHelpers/index.js +1 -0
  106. package/distHelpers/index.js.map +1 -1
  107. package/distNodeJS/generate_address_space.js +5 -1
  108. package/distNodeJS/generate_address_space.js.map +1 -1
  109. package/impl/address_space_change_event_tools.ts +4 -1
  110. package/impl/address_space_private.ts +5 -17
  111. package/impl/alarms_and_conditions/deviation_alarm_helper.ts +3 -0
  112. package/impl/alarms_and_conditions/ua_acknowledgeable_condition_impl.ts +53 -41
  113. package/impl/alarms_and_conditions/ua_alarm_condition_impl.ts +262 -111
  114. package/impl/alarms_and_conditions/ua_condition_impl.ts +94 -20
  115. package/impl/alarms_and_conditions/ua_exclusive_deviation_alarm_impl.ts +14 -3
  116. package/impl/alarms_and_conditions/ua_exclusive_limit_alarm_impl.ts +4 -4
  117. package/impl/alarms_and_conditions/ua_limit_alarm_impl.ts +22 -5
  118. package/impl/alarms_and_conditions/ua_non_exclusive_deviation_alarm_impl.ts +14 -3
  119. package/impl/alarms_and_conditions/ua_non_exclusive_limit_alarm_impl.ts +2 -2
  120. package/impl/alarms_and_conditions/ua_off_normal_alarm_impl.ts +2 -2
  121. package/impl/base_node_impl.ts +31 -0
  122. package/impl/base_node_private.ts +2 -0
  123. package/impl/data_access/adjust_datavalue_status_code.ts +36 -7
  124. package/impl/data_access/semantics_changed.ts +102 -0
  125. package/impl/data_access/ua_multistate_discrete_impl.ts +0 -2
  126. package/impl/data_access/ua_two_state_discrete_impl.ts +6 -16
  127. package/impl/historical_access/read_processed_details_hook.ts +35 -0
  128. package/impl/namespace_impl.ts +2 -6
  129. package/impl/nodeid_manager.ts +27 -5
  130. package/impl/ua_object_impl.ts +13 -1
  131. package/impl/ua_object_type_impl.ts +1 -0
  132. package/impl/ua_variable_impl.ts +12 -1
  133. package/impl/ua_variable_type_impl.ts +1 -0
  134. package/package.json +39 -39
  135. package/source_nodejs/generate_address_space.ts +6 -1
@@ -2,7 +2,7 @@
2
2
  * @module node-opcua-address-space.AlarmsAndConditions
3
3
  */
4
4
  import { isDeepStrictEqual as isEqual } from "node:util";
5
- import type { BaseNode, INamespace, UAEventType, UAProperty, UAVariable } from "node-opcua-address-space-base";
5
+ import type { BaseNode, INamespace, UAEventType, UAObject, UAProperty, UAVariable } from "node-opcua-address-space-base";
6
6
  import { assert } from "node-opcua-assert";
7
7
  import { NodeClass } from "node-opcua-data-model";
8
8
  import type { DataValue } from "node-opcua-data-value";
@@ -12,6 +12,7 @@ import { StatusCodes } from "node-opcua-status-code";
12
12
  import { DataType, type VariantOptions } from "node-opcua-variant";
13
13
  import type { ConditionInfo } from "../../api/interfaces/alarms_and_conditions/condition_info_i.js";
14
14
  import type { InstantiateAlarmConditionOptions } from "../../api/interfaces/alarms_and_conditions/instantiate_alarm_condition_options.js";
15
+ import type { PromoteToAlarmOptions } from "../../api/interfaces/alarms_and_conditions/promote_to_alarm_options.js";
15
16
  import type { UAAlarmConditionEx } from "../../api/interfaces/alarms_and_conditions/ua_alarm_condition_ex.js";
16
17
  import type { UAShelvedStateMachineEx } from "../../api/interfaces/state_machine/ua_shelved_state_machine_ex.js";
17
18
  import type { UATwoStateVariableEx } from "../../api/ua_two_state_variable_ex.js";
@@ -23,7 +24,12 @@ import {
23
24
  } from "../state_machine/ua_shelving_state_machine_ex.js";
24
25
  import { _install_TwoStateVariable_machinery } from "../state_machine/ua_two_state_variable.js";
25
26
  import { ConditionInfoImpl } from "./condition_info_impl.js";
26
- import { UAAcknowledgeableConditionImpl, UAAcknowledgeableConditionImplBase } from "./ua_acknowledgeable_condition_impl.js";
27
+ import {
28
+ _initialize_acknowledgeable_condition_node,
29
+ UAAcknowledgeableConditionImpl,
30
+ UAAcknowledgeableConditionImplBase
31
+ } from "./ua_acknowledgeable_condition_impl.js";
32
+ import { _initialize_condition_node, type ConditionInitializationOptions } from "./ua_condition_impl.js";
27
33
 
28
34
  const debugLog = make_debugLog("ua_alarm_condition_impl");
29
35
  const doDebug = checkDebugFlag("ua_alarm_condition_impl");
@@ -62,6 +68,7 @@ export class UAAlarmConditionImplBase extends UAAcknowledgeableConditionImplBase
62
68
  const addressSpace = namespace.addressSpace;
63
69
  // xx assert(Object.prototype.hasOwnProperty.call(options,"conditionOf")); // must provide a conditionOf
64
70
  assert(Object.hasOwn(options, "inputNode")); // must provide a inputNode
71
+ assert(options.inputNode, " must provide options.inputNode (NodeId or BaseNode object)");
65
72
  const alarmConditionType = addressSpace.findEventType(alarmConditionTypeId);
66
73
 
67
74
  /* c8 ignore next */
@@ -99,114 +106,8 @@ export class UAAlarmConditionImplBase extends UAAcknowledgeableConditionImplBase
99
106
  options,
100
107
  data
101
108
  ) as unknown as UAAlarmConditionImpl;
102
- Object.setPrototypeOf(alarmNode, UAAlarmConditionImpl.prototype);
103
-
104
- // ----------------------- Install Alarm specifics
105
- //
106
-
107
- // Specs 1.03:
108
- // ActiveState/Id when set to TRUE indicates that the situation the Condition is representing
109
- // currently exists. When a Condition instance is in the inactive state (ActiveState/Id when set to
110
- // FALSE) it is representing a situation that has returned to a normal state. The transitions of
111
- // Conditions to the inactive and Active states are triggered by Server specific actions. Sub-
112
- // Types of the AlarmConditionType specified later in this document will have sub-state models
113
- // that further define the Active state. Recommended state names are described in Annex A.
114
- // install activeState - Mandatory
115
-
116
- /**
117
- * @property activeState
118
- * @type {UATwoStateVariable}
119
- */
120
- _install_TwoStateVariable_machinery(alarmNode.activeState, {
121
- falseState: "Inactive",
122
- trueState: "Active"
123
- });
124
-
125
- alarmNode.currentBranch().setActiveState(false);
126
-
127
- // Specs 1.03:
128
- /**
129
- *
130
- * SuppressState is used internally by a Server to automatically suppress Alarms due to system
131
- * specific reasons. For example a system may be configured to suppress Alarms that are
132
- * associated with machinery that is shutdown, such as a low level Alarm for a tank that is
133
- * currently not in use.
134
- *
135
- * @property suppressedState
136
- * @type UATwoStateVariable
137
- */
138
- if (alarmNode.suppressedState) {
139
- // install activeState - Optional
140
- _install_TwoStateVariable_machinery(alarmNode.suppressedState, {
141
- falseState: "Unsuppressed",
142
- trueState: "Suppressed"
143
- });
144
- }
145
- // Specs 1.03:
146
- /**
147
- * ShelvingState suggests whether an Alarm shall (temporarily) be prevented from being
148
- * displayed to the user. It is quite often used to block nuisance Alarms.
149
- *
150
- * @property shelvingState
151
- * @type ShelvingStateMachine
152
- */
153
- if (alarmNode.shelvingState) {
154
- UAShelvedStateMachineExImplBase.promote(alarmNode.shelvingState);
155
- }
156
-
157
- // SuppressedOrShelved : Mandatory
158
- // install suppressedOrShelved automatic detection
159
- /**
160
- * The SuppressedState and the ShelvingState together result in the SuppressedOrShelved status of the
161
- * Condition. When an Alarm is in one of the states, the SuppressedOrShelved property will be set TRUE
162
- * and this Alarm is then typically not displayed by the Client. State transitions associated with the
163
- * Alarm do occur, but they are not typically displayed by the Clients as long as the Alarm remains in
164
- * either the Suppressed or Shelved state.
165
- * The dataType is Boolean.
166
- * @property suppressedState
167
- * @type UAVariable
168
- *
169
- */
170
- if (alarmNode.suppressedState) {
171
- alarmNode.suppressedState.on("value_changed", (_newDataValue: DataValue) => {
172
- _update_suppressedOrShelved(alarmNode);
173
- });
174
- }
175
- if (alarmNode.shelvingState) {
176
- alarmNode.shelvingState.currentState.on("value_changed", (_newDataValue: DataValue) => {
177
- _update_suppressedOrShelved(alarmNode);
178
- });
179
- }
180
- _update_suppressedOrShelved(alarmNode);
181
109
 
182
- /**
183
- * The optional Property MaxTimeShelved is used to set the maximum time that an Alarm Condition may be shelved.
184
- * The value is expressed as duration. Systems can use this Property to prevent permanent Shelving of an Alarm.
185
- * If this Property is present it will be an upper limit on the duration passed into a TimedShelve Method call.
186
- * If a value that exceeds the value of this property is passed to the TimedShelve Method,
187
- * than a BadShelvingTimeOutOfRange error code is returned on the call. If this Property is present it will
188
- * also be enforced for the OneShotShelved state, in that an Alarm Condition will transition to the Unshelved
189
- * state from the OneShotShelved state if the duration specified in this Property expires following a
190
- * OneShotShelve operation without a change of any of the other items associated with the Condition.
191
- *
192
- * @property maxTimeShelved
193
- * @type {UAVariable}
194
- */
195
- if (alarmNode.maxTimeShelved) {
196
- options.maxTimeShelved = options.maxTimeShelved || 60.0 * 1000; // 60 seconds
197
- alarmNode.maxTimeShelved.setValueFromSource({
198
- dataType: "Duration",
199
- value: options.maxTimeShelved
200
- });
201
- }
202
-
203
- // ---------- install inputNode
204
- assert(options.inputNode, " must provide options.inputNode (NodeId or BaseNode object)");
205
- alarmNode.installInputNodeMonitoring(options.inputNode);
206
-
207
- assert(alarmNode instanceof UAAcknowledgeableConditionImpl);
208
- assert(alarmNode instanceof UAAlarmConditionImpl);
209
- return alarmNode;
110
+ return _initialize_alarm_condition_node(alarmNode, options);
210
111
  }
211
112
 
212
113
  public dispose(): void {
@@ -323,10 +224,36 @@ export class UAAlarmConditionImplBase extends UAAcknowledgeableConditionImplBase
323
224
  this._onInputDataValueChange(dataValue);
324
225
  }
325
226
 
326
- protected _onInputDataValueChange(_newValue: DataValue): void {
227
+ /**
228
+ * What the alarm does when the value of its input node changes. Assign to it to give an
229
+ * alarm a state machine of its own, without deriving from anything.
230
+ *
231
+ * @example
232
+ *
233
+ * ```ts
234
+ * const myAlarm = namespace.instantiateAlarmCondition("AlarmConditionType", { ... });
235
+ * myAlarm.onInputDataValueChange = (newValue) => {
236
+ * const tooHot = newValue.value.value > 80;
237
+ * if (tooHot !== myAlarm.activeState.getValue()) {
238
+ * myAlarm.signalNewCondition(tooHot ? "Active" : "Inactive", tooHot, `${newValue.value.value}`);
239
+ * }
240
+ * };
241
+ * ```
242
+ *
243
+ * The default does nothing: a plain AlarmConditionType has no idea what its input means.
244
+ * The alarm types that do (limit alarms, off-normal alarms) override this.
245
+ */
246
+ public onInputDataValueChange(_newValue: DataValue): void {
327
247
  /** */
328
248
  }
329
249
 
250
+ /**
251
+ * @deprecated assign {@link onInputDataValueChange} instead; this delegates to it.
252
+ */
253
+ protected _onInputDataValueChange(newValue: DataValue): void {
254
+ this.onInputDataValueChange(newValue);
255
+ }
256
+
330
257
  /**
331
258
  * install mechanism that listen to input node datavalue changes so that alarm status
332
259
  * can be automatically updated appropriately.
@@ -464,7 +391,21 @@ export class UAAlarmConditionImplBase extends UAAcknowledgeableConditionImplBase
464
391
  this.currentBranch().setActiveState(false);
465
392
  this.currentBranch().setAckedState(true);
466
393
  }
394
+ /**
395
+ * @deprecated call {@link signalNewCondition} instead; this delegates to it.
396
+ */
467
397
  public _signalNewCondition(stateName: string | null, isActive: boolean, value: string): void {
398
+ this.signalNewCondition(stateName, isActive, value);
399
+ }
400
+
401
+ /**
402
+ * Raise a new condition event for this alarm.
403
+ *
404
+ * Call this only when the condition has actually changed: it throws when the new
405
+ * ConditionInfo is equal to the current one, because raising an event that reports
406
+ * nothing new is a bug in the caller rather than a state the alarm can represent.
407
+ */
408
+ public signalNewCondition(stateName: string | null, isActive: boolean, value: string): void {
468
409
  // xx if(stateName === null) {
469
410
  // xx alarm.currentBranch().setActiveState(false);
470
411
  // xx alarm.currentBranch().setAckedState(true);
@@ -480,7 +421,7 @@ export class UAAlarmConditionImplBase extends UAAcknowledgeableConditionImplBase
480
421
  // `calculateConditionInfo` is reached by the default below. Both work.
481
422
  const newConditionInfo = this._calculateConditionInfo(stateName, isActive, value, oldConditionInfo);
482
423
 
483
- // detect potential internal bugs due to misused of _signalNewCondition
424
+ // detect potential internal bugs due to misused of signalNewCondition
484
425
  if (isEqual(oldConditionInfo, newConditionInfo)) {
485
426
  // c8 ignore next
486
427
  if (doDebug) {
@@ -523,3 +464,213 @@ export class UAAlarmConditionImplBase extends UAAcknowledgeableConditionImplBase
523
464
  export type UAAlarmConditionImpl = UAAlarmConditionImplBase;
524
465
  /** @internal */
525
466
  export const UAAlarmConditionImpl = UAAlarmConditionImplBase;
467
+
468
+ /**
469
+ * The part of {@link InstantiateAlarmConditionOptions} that the initialization step below reads.
470
+ *
471
+ * @internal
472
+ */
473
+ export type AlarmConditionInitializationOptions = ConditionInitializationOptions & {
474
+ maxTimeShelved?: number;
475
+ inputNode?: UAVariable | NodeId;
476
+ };
477
+
478
+ /**
479
+ * The alarm half of the wiring, everything that follows the creation of the node.
480
+ *
481
+ * Shared by {@link UAAlarmConditionImplBase.instantiate} and by {@link promoteToAlarm}, so a node
482
+ * that was created some other way ends up with exactly the same machinery rather than an
483
+ * approximation of it. `inputNode` is optional here because a node being promoted may already
484
+ * carry its InputNode property, or may be given one later.
485
+ *
486
+ * @internal
487
+ */
488
+ export function _initialize_alarm_condition_node(
489
+ alarmNode: UAAlarmConditionImpl,
490
+ options: AlarmConditionInitializationOptions
491
+ ): UAAlarmConditionImpl {
492
+ Object.setPrototypeOf(alarmNode, UAAlarmConditionImpl.prototype);
493
+
494
+ // ----------------------- Install Alarm specifics
495
+ //
496
+
497
+ // Specs 1.03:
498
+ // ActiveState/Id when set to TRUE indicates that the situation the Condition is representing
499
+ // currently exists. When a Condition instance is in the inactive state (ActiveState/Id when set to
500
+ // FALSE) it is representing a situation that has returned to a normal state. The transitions of
501
+ // Conditions to the inactive and Active states are triggered by Server specific actions. Sub-
502
+ // Types of the AlarmConditionType specified later in this document will have sub-state models
503
+ // that further define the Active state. Recommended state names are described in Annex A.
504
+ // install activeState - Mandatory
505
+
506
+ /**
507
+ * @property activeState
508
+ * @type {UATwoStateVariable}
509
+ */
510
+ _install_TwoStateVariable_machinery(alarmNode.activeState, {
511
+ falseState: "Inactive",
512
+ trueState: "Active"
513
+ });
514
+
515
+ alarmNode.currentBranch().setActiveState(false);
516
+
517
+ // Specs 1.03:
518
+ /**
519
+ *
520
+ * SuppressState is used internally by a Server to automatically suppress Alarms due to system
521
+ * specific reasons. For example a system may be configured to suppress Alarms that are
522
+ * associated with machinery that is shutdown, such as a low level Alarm for a tank that is
523
+ * currently not in use.
524
+ *
525
+ * @property suppressedState
526
+ * @type UATwoStateVariable
527
+ */
528
+ if (alarmNode.suppressedState) {
529
+ // install activeState - Optional
530
+ _install_TwoStateVariable_machinery(alarmNode.suppressedState, {
531
+ falseState: "Unsuppressed",
532
+ trueState: "Suppressed"
533
+ });
534
+ }
535
+ // Specs 1.03:
536
+ /**
537
+ * ShelvingState suggests whether an Alarm shall (temporarily) be prevented from being
538
+ * displayed to the user. It is quite often used to block nuisance Alarms.
539
+ *
540
+ * @property shelvingState
541
+ * @type ShelvingStateMachine
542
+ */
543
+ if (alarmNode.shelvingState) {
544
+ UAShelvedStateMachineExImplBase.promote(alarmNode.shelvingState);
545
+ }
546
+
547
+ // SuppressedOrShelved : Mandatory
548
+ // install suppressedOrShelved automatic detection
549
+ /**
550
+ * The SuppressedState and the ShelvingState together result in the SuppressedOrShelved status of the
551
+ * Condition. When an Alarm is in one of the states, the SuppressedOrShelved property will be set TRUE
552
+ * and this Alarm is then typically not displayed by the Client. State transitions associated with the
553
+ * Alarm do occur, but they are not typically displayed by the Clients as long as the Alarm remains in
554
+ * either the Suppressed or Shelved state.
555
+ * The dataType is Boolean.
556
+ * @property suppressedState
557
+ * @type UAVariable
558
+ *
559
+ */
560
+ if (alarmNode.suppressedState) {
561
+ alarmNode.suppressedState.on("value_changed", (_newDataValue: DataValue) => {
562
+ _update_suppressedOrShelved(alarmNode);
563
+ });
564
+ }
565
+ if (alarmNode.shelvingState) {
566
+ alarmNode.shelvingState.currentState.on("value_changed", (_newDataValue: DataValue) => {
567
+ _update_suppressedOrShelved(alarmNode);
568
+ });
569
+ }
570
+ _update_suppressedOrShelved(alarmNode);
571
+
572
+ /**
573
+ * The optional Property MaxTimeShelved is used to set the maximum time that an Alarm Condition may be shelved.
574
+ * The value is expressed as duration. Systems can use this Property to prevent permanent Shelving of an Alarm.
575
+ * If this Property is present it will be an upper limit on the duration passed into a TimedShelve Method call.
576
+ * If a value that exceeds the value of this property is passed to the TimedShelve Method,
577
+ * than a BadShelvingTimeOutOfRange error code is returned on the call. If this Property is present it will
578
+ * also be enforced for the OneShotShelved state, in that an Alarm Condition will transition to the Unshelved
579
+ * state from the OneShotShelved state if the duration specified in this Property expires following a
580
+ * OneShotShelve operation without a change of any of the other items associated with the Condition.
581
+ *
582
+ * @property maxTimeShelved
583
+ * @type {UAVariable}
584
+ */
585
+ if (alarmNode.maxTimeShelved) {
586
+ options.maxTimeShelved = options.maxTimeShelved || 60.0 * 1000; // 60 seconds
587
+ alarmNode.maxTimeShelved.setValueFromSource({
588
+ dataType: "Duration",
589
+ value: options.maxTimeShelved
590
+ });
591
+ }
592
+
593
+ // ---------- install inputNode
594
+ if (options.inputNode) {
595
+ alarmNode.installInputNodeMonitoring(options.inputNode);
596
+ }
597
+
598
+ assert(alarmNode instanceof UAAcknowledgeableConditionImpl);
599
+ assert(alarmNode instanceof UAAlarmConditionImpl);
600
+ return alarmNode;
601
+ }
602
+
603
+ /**
604
+ * Give an existing node the behaviour of the alarm its type says it is.
605
+ *
606
+ * A node only becomes a working alarm when `namespace.instantiateAlarmCondition` builds it; a node
607
+ * that arrived any other way - `objectType.instantiate()`, or a nodeset XML - carries the right
608
+ * children but none of the machinery, so `activateAlarm` and `signalNewCondition` are simply not
609
+ * there. This retypes the node in place, keeping its identity in the address space index and every
610
+ * reference already pointing at it, and then runs the same initialization the factory runs.
611
+ *
612
+ * Limit alarms are refused rather than half-promoted: their behaviour lives in
613
+ * UALimitAlarmImpl and its subclasses, and a node wearing the plain alarm prototype would look
614
+ * promoted while ignoring its own limit set points.
615
+ */
616
+ export function promoteToAlarm(node: UAObject, options?: PromoteToAlarmOptions): UAAlarmConditionEx {
617
+ if (node instanceof UAAlarmConditionImplBase) {
618
+ return node as unknown as UAAlarmConditionImpl; // already promoted
619
+ }
620
+ const addressSpace = node.addressSpace;
621
+ const typeDefinition = node.typeDefinitionObj;
622
+
623
+ const alarmConditionType = addressSpace.findObjectType("AlarmConditionType");
624
+ /* c8 ignore next */
625
+ if (!alarmConditionType) {
626
+ throw new Error("promoteToAlarm: cannot find AlarmConditionType - is the standard nodeset loaded ?");
627
+ }
628
+ if (!typeDefinition || (typeDefinition !== alarmConditionType && !typeDefinition.isSubtypeOf(alarmConditionType))) {
629
+ throw new Error(
630
+ `promoteToAlarm: ${node.browseName.toString()} has type definition ` +
631
+ `${typeDefinition ? typeDefinition.browseName.toString() : "<none>"} ` +
632
+ "which does not derive from AlarmConditionType - only alarms can be promoted"
633
+ );
634
+ }
635
+ const limitAlarmType = addressSpace.findObjectType("LimitAlarmType");
636
+ if (limitAlarmType && (typeDefinition === limitAlarmType || typeDefinition.isSubtypeOf(limitAlarmType))) {
637
+ throw new Error(
638
+ `promoteToAlarm: ${node.browseName.toString()} is a ${typeDefinition.browseName.toString()}, ` +
639
+ "a limit alarm - promoting limit alarms is not supported, use namespace.instantiateLimitAlarm instead"
640
+ );
641
+ }
642
+ // CertificateExpirationAlarmType carries behaviour of its own - promoteToCertificateExpirationAlarm
643
+ // starts an expiry timer and registers a shutdown task. Promoting one here would give it plain
644
+ // alarm behaviour and silently drop that, which is worse than refusing: the node would look
645
+ // promoted and never fire.
646
+ const certificateExpirationAlarmType = addressSpace.findObjectType("CertificateExpirationAlarmType");
647
+ if (
648
+ certificateExpirationAlarmType &&
649
+ (typeDefinition === certificateExpirationAlarmType || typeDefinition.isSubtypeOf(certificateExpirationAlarmType))
650
+ ) {
651
+ throw new Error(
652
+ `promoteToAlarm: ${node.browseName.toString()} is a ${typeDefinition.browseName.toString()}, ` +
653
+ "which has its own expiry machinery - use promoteToCertificateExpirationAlarm instead"
654
+ );
655
+ }
656
+ const alarmConditionEventType = addressSpace.findEventType(typeDefinition.nodeId);
657
+ /* c8 ignore next */
658
+ if (!alarmConditionEventType) {
659
+ throw new Error(`promoteToAlarm: cannot find event type for ${typeDefinition.browseName.toString()}`);
660
+ }
661
+
662
+ // conditionSource has to be an own property even when it is absent: the condition
663
+ // initialization tells "no source" from "source not considered" by its presence.
664
+ const initializationOptions: AlarmConditionInitializationOptions = {
665
+ conditionClass: options?.conditionClass,
666
+ conditionName: options?.conditionName || node.browseName.name || undefined,
667
+ conditionSource: options?.conditionSource ?? null,
668
+ inputNode: options?.inputNode,
669
+ maxTimeShelved: options?.maxTimeShelved
670
+ };
671
+
672
+ const conditionNode = node as unknown as UAAlarmConditionImpl;
673
+ _initialize_condition_node(node.namespace, conditionNode, alarmConditionEventType, initializationOptions);
674
+ _initialize_acknowledgeable_condition_node(conditionNode);
675
+ return _initialize_alarm_condition_node(conditionNode, initializationOptions);
676
+ }
@@ -749,6 +749,40 @@ function UACondition_instantiate(
749
749
  namespace: options.namespace || namespace
750
750
  };
751
751
  const conditionNode = conditionType.instantiate(options2) as unknown as UAConditionImpl;
752
+ return _initialize_condition_node(namespace, conditionNode, conditionType, options, data);
753
+ }
754
+
755
+ /**
756
+ * The part of {@link InstantiateConditionOptions} that the initialization step below reads.
757
+ *
758
+ * Named separately so a caller that is wiring an existing node - which has no browseName or
759
+ * parent to give - does not have to invent the creation-time half of the options.
760
+ *
761
+ * @internal
762
+ */
763
+ export type ConditionInitializationOptions = Pick<
764
+ InstantiateConditionOptions,
765
+ "conditionSource" | "conditionOf" | "conditionClass" | "conditionName"
766
+ >;
767
+
768
+ /**
769
+ * Everything that turns a freshly created node into a working condition.
770
+ *
771
+ * Split out of {@link UACondition_instantiate} so that a node that already exists - one built by
772
+ * `objectType.instantiate` or loaded from a nodeset - can be given the very same wiring without
773
+ * being rebuilt, and so the two routes cannot drift apart.
774
+ *
775
+ * @internal
776
+ */
777
+ export function _initialize_condition_node(
778
+ namespace: INamespace,
779
+ conditionNode: UAConditionImpl,
780
+ conditionType: UAEventType,
781
+ options: ConditionInitializationOptions,
782
+ data?: Record<string, VariantOptions>
783
+ ): UAConditionEx {
784
+ const addressSpace = namespace.addressSpace as AddressSpacePrivate;
785
+
752
786
  Object.setPrototypeOf(conditionNode, UAConditionImpl.prototype);
753
787
  conditionNode.initialize();
754
788
 
@@ -1075,9 +1109,11 @@ function _condition_refresh_method(
1075
1109
  }
1076
1110
  const subscriptionId = inputArguments[0].value;
1077
1111
 
1112
+ // a rejected argument is answered in the Call result, never thrown: the Call service
1113
+ // turns an exception into Bad_InternalError and the CTT reads that as a broken server
1078
1114
  let statusCode = _check_subscription_id_is_valid(subscriptionId, context);
1079
1115
  if (statusCode.isNotGood()) {
1080
- return statusCode;
1116
+ return callback(null, { statusCode });
1081
1117
  }
1082
1118
 
1083
1119
  statusCode = _perform_condition_refresh(addressSpace, inputArguments, context);
@@ -1090,6 +1126,7 @@ function _perform_condition_refresh(addressSpace: AddressSpacePrivate, _inputArg
1090
1126
  // --- possible StatusCodes:
1091
1127
  //
1092
1128
  // Bad_SubscriptionIdInvalid See Part 4 for the description of this result code
1129
+ // (checked by the caller, before we get here)
1093
1130
  // Bad_RefreshInProgress See Table 74 for the description of this result code
1094
1131
  // Bad_UserAccessDenied The Method was not called in the context of the Session
1095
1132
  // that owns the Subscription
@@ -1101,8 +1138,6 @@ function _perform_condition_refresh(addressSpace: AddressSpacePrivate, _inputArg
1101
1138
  return StatusCodes.BadRefreshInProgress;
1102
1139
  }
1103
1140
 
1104
- addressSpace._condition_refresh_in_progress = true;
1105
-
1106
1141
  const server = context.object?.addressSpace.rootFolder.objects.server;
1107
1142
  const refreshStartEventType = addressSpace.findEventType("RefreshStartEventType");
1108
1143
  if (!refreshStartEventType) {
@@ -1113,17 +1148,21 @@ function _perform_condition_refresh(addressSpace: AddressSpacePrivate, _inputArg
1113
1148
  throw new Error("cannot find RefreshEndEventType");
1114
1149
  }
1115
1150
 
1116
- server?.raiseEvent(refreshStartEventType, {});
1117
- // todo : resend retained conditions
1118
-
1119
- const _server = server as unknown as { _conditionRefresh: () => void };
1120
- // starting from server object ..
1121
- // evaluated all --> hasNotifier/hasEventSource -> node
1122
- _server._conditionRefresh();
1123
-
1124
- server?.raiseEvent(refreshEndEventType, {});
1125
-
1126
- addressSpace._condition_refresh_in_progress = false;
1151
+ addressSpace._condition_refresh_in_progress = true;
1152
+ try {
1153
+ server?.raiseEvent(refreshStartEventType, {});
1154
+
1155
+ const _server = server as unknown as { _conditionRefresh: () => void };
1156
+ // starting from server object ..
1157
+ // evaluated all --> hasNotifier/hasEventSource -> node
1158
+ _server._conditionRefresh();
1159
+
1160
+ server?.raiseEvent(refreshEndEventType, {});
1161
+ } finally {
1162
+ // whatever a condition's event handler throws, the next refresh must not be
1163
+ // answered Bad_RefreshInProgress for ever
1164
+ addressSpace._condition_refresh_in_progress = false;
1165
+ }
1127
1166
 
1128
1167
  return StatusCodes.Good;
1129
1168
  }
@@ -1146,10 +1185,19 @@ function _condition_refresh2_method(
1146
1185
  debugLog(chalk.cyan.bgWhite(" ConditionType.conditionRefresh2 !"));
1147
1186
  }
1148
1187
 
1149
- // xx var subscriptionId = inputArguments[0].value;
1150
- // xx var monitoredItemId = inputArguments[1].value;
1188
+ const subscriptionId = inputArguments[0].value;
1189
+ const monitoredItemId = inputArguments[1].value;
1151
1190
 
1152
- const statusCode = _perform_condition_refresh(addressSpace, inputArguments, context);
1191
+ let statusCode = _check_subscription_id_is_valid(subscriptionId, context);
1192
+ if (statusCode.isNotGood()) {
1193
+ return callback(null, { statusCode });
1194
+ }
1195
+ statusCode = _check_monitored_item_id_is_valid(subscriptionId, monitoredItemId, context);
1196
+ if (statusCode.isNotGood()) {
1197
+ return callback(null, { statusCode });
1198
+ }
1199
+
1200
+ statusCode = _perform_condition_refresh(addressSpace, inputArguments, context);
1153
1201
  return callback(null, {
1154
1202
  statusCode
1155
1203
  });
@@ -1280,7 +1328,33 @@ function _getCompositeKey(node: BaseNode, key: string): UAVariableImpl {
1280
1328
  * @param context {Object}
1281
1329
  * @private
1282
1330
  */
1283
- function _check_subscription_id_is_valid(_subscriptionId: number, _context: ISessionContext) {
1284
- /// todo: return StatusCodes.BadSubscriptionIdInvalid; if subscriptionId doesn't belong to session...
1285
- return StatusCodes.Good;
1331
+ function _check_subscription_id_is_valid(subscriptionId: number, context: ISessionContext): StatusCode {
1332
+ // Part 9 5.5.7: Bad_SubscriptionIdInvalid when the SubscriptionId is not one of the
1333
+ // calling Session's. A Subscription of another Session is not reachable from this one
1334
+ // (Part 4: SubscriptionIds are Session-scoped), so it gets the same answer.
1335
+ const session = context.session;
1336
+ if (!session || typeof session.getSubscription !== "function") {
1337
+ // no Session behind the call (an in-process caller, a PseudoSession, a unit test):
1338
+ // there is nothing to check the argument against
1339
+ return StatusCodes.Good;
1340
+ }
1341
+ return session.getSubscription(subscriptionId) ? StatusCodes.Good : StatusCodes.BadSubscriptionIdInvalid;
1342
+ }
1343
+
1344
+ /**
1345
+ * ConditionRefresh2 (Part 9 5.5.8): the MonitoredItemId must name a MonitoredItem of the
1346
+ * given Subscription, else Bad_MonitoredItemIdInvalid. Call after
1347
+ * _check_subscription_id_is_valid, which has established that the Subscription is ours.
1348
+ * @private
1349
+ */
1350
+ function _check_monitored_item_id_is_valid(subscriptionId: number, monitoredItemId: number, context: ISessionContext): StatusCode {
1351
+ const session = context.session;
1352
+ if (!session || typeof session.getSubscription !== "function") {
1353
+ return StatusCodes.Good;
1354
+ }
1355
+ const subscription = session.getSubscription(subscriptionId);
1356
+ if (!subscription) {
1357
+ return StatusCodes.BadSubscriptionIdInvalid;
1358
+ }
1359
+ return subscription.getMonitoredItem(monitoredItemId) ? StatusCodes.Good : StatusCodes.BadMonitoredItemIdInvalid;
1286
1360
  }
@@ -70,22 +70,33 @@ export class UAExclusiveDeviationAlarmImplBase extends UAExclusiveLimitAlarmImpl
70
70
  return DeviationAlarmHelper_getSetpointValue.call(this);
71
71
  }
72
72
 
73
- public _onSetpointDataValueChange(dataValue: DataValue): void {
73
+ /**
74
+ * What the alarm does when the value of its setpoint node changes: re-evaluate the state
75
+ * of the alarm against the new setpoint, since the deviation it watches has just moved.
76
+ */
77
+ public onSetpointDataValueChange(dataValue: DataValue): void {
74
78
  DeviationAlarmHelper_onSetpointDataValueChange.call(this, dataValue);
75
79
  }
76
80
 
81
+ /**
82
+ * @deprecated assign {@link onSetpointDataValueChange} instead; this delegates to it.
83
+ */
84
+ public _onSetpointDataValueChange(dataValue: DataValue): void {
85
+ this.onSetpointDataValueChange(dataValue);
86
+ }
87
+
77
88
  public _install_setpoint(options: InstallSetPointOptions): void {
78
89
  DeviationAlarmHelper_install_setpoint.call(this, options);
79
90
  }
80
91
 
81
- public _setStateBasedOnInputValue(value: number): void {
92
+ public setStateBasedOnInputValue(value: number): void {
82
93
  const setpointValue = this.getSetpointValue();
83
94
  if (setpointValue === null) {
84
95
  return;
85
96
  }
86
97
  assert(Number.isFinite(setpointValue));
87
98
  // call base class implementation
88
- UAExclusiveLimitAlarmImpl.prototype._setStateBasedOnInputValue.call(this, value - setpointValue);
99
+ UAExclusiveLimitAlarmImpl.prototype.setStateBasedOnInputValue.call(this, value - setpointValue);
89
100
  }
90
101
  }
91
102
  /** @internal */
@@ -62,7 +62,7 @@ export class UAExclusiveLimitAlarmImplBase extends UALimitAlarmImpl implements U
62
62
 
63
63
  return alarm as UAExclusiveLimitAlarmImpl;
64
64
  }
65
- public _signalNewCondition(stateName: string | null, isActive: boolean, value: string): void {
65
+ public signalNewCondition(stateName: string | null, isActive: boolean, value: string): void {
66
66
  assert(stateName === null || typeof isActive === "boolean");
67
67
  assert(validState.indexOf(stateName) >= 0, `must have a valid state : ${stateName}`);
68
68
 
@@ -75,10 +75,10 @@ export class UAExclusiveLimitAlarmImplBase extends UALimitAlarmImpl implements U
75
75
  assert(stateName === null);
76
76
  this.limitState.setState(stateName);
77
77
  }
78
- super._signalNewCondition(stateName, isActive, value);
78
+ super.signalNewCondition(stateName, isActive, value);
79
79
  }
80
80
 
81
- public _setStateBasedOnInputValue(value: number): void {
81
+ public setStateBasedOnInputValue(value: number): void {
82
82
  assert(Number.isFinite(value));
83
83
  let isActive = false;
84
84
 
@@ -101,7 +101,7 @@ export class UAExclusiveLimitAlarmImplBase extends UALimitAlarmImpl implements U
101
101
  }
102
102
 
103
103
  if (state !== oldState) {
104
- this._signalNewCondition(state, isActive, value.toFixed(3));
104
+ this.signalNewCondition(state, isActive, value.toFixed(3));
105
105
  }
106
106
  }
107
107
  }