node-opcua-address-space 2.92.0 → 2.94.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-opcua-address-space",
3
- "version": "2.92.0",
3
+ "version": "2.94.0",
4
4
  "description": "pure nodejs OPCUA SDK - module -address-space",
5
5
  "main": "./dist/src/index_current.js",
6
6
  "types": "./dist/source/index.d.ts",
@@ -84,5 +84,5 @@
84
84
  "internet of things"
85
85
  ],
86
86
  "homepage": "http://node-opcua.github.io/",
87
- "gitHead": "6ee0f52395af2c1f4f03518e675dc3f6edb7a455"
87
+ "gitHead": "c3a1d978ab5a0af8addb53750e2652adcdcd160d"
88
88
  }
package/source/index.ts CHANGED
@@ -19,7 +19,6 @@ export * from "./interfaces/data_access/ua_two_state_discrete_ex";
19
19
  export * from "./interfaces/data_access/ua_y_array_item_ex";
20
20
  export * from "./interfaces/extension_object_constructor";
21
21
 
22
-
23
22
  export * from "./interfaces/alarms_and_conditions/condition_info_i";
24
23
  export * from "./interfaces/alarms_and_conditions/condition_snapshot";
25
24
  export * from "./interfaces/alarms_and_conditions/instantiate_alarm_condition_options";
@@ -41,7 +40,7 @@ export * from "./interfaces/alarms_and_conditions/ua_non_exclusive_limit_alarm_e
41
40
  export { promoteToMultiStateDiscrete } from "../src/data_access/ua_multistate_discrete_impl";
42
41
  export { promoteToMultiStateValueDiscrete } from "../src/data_access/ua_multistate_value_discrete_impl";
43
42
  export { promoteToTwoStateDiscrete } from "../src/data_access/ua_two_state_discrete_impl";
44
-
43
+ export { validateDataType } from "../src/data_access/ua_multistate_value_discrete_impl";
45
44
 
46
45
  export * from "./ua_root_folder";
47
46
  export * from "./session_context";
@@ -73,4 +72,4 @@ export * from "./set_namespace_meta_data";
73
72
 
74
73
  export * from "node-opcua-address-space-base";
75
74
  export * from "node-opcua-nodeset-ua";
76
- export * from "../src/nodeset_tools/construct_namespace_dependency";
75
+ export * from "../src/nodeset_tools/construct_namespace_dependency";
@@ -122,6 +122,9 @@ export function innerBrowseNext(
122
122
  );
123
123
  callback!(null, results);
124
124
  }
125
+
126
+ const $addressSpace = Symbol("addressSpace");
127
+ const $context = Symbol("context");
125
128
  /**
126
129
  * Pseudo session is an helper object that exposes the same async methods
127
130
  * than the ClientSession. It can be used on a server address space.
@@ -136,13 +139,13 @@ export class PseudoSession implements IBasicSession {
136
139
  public requestedMaxReferencesPerNode = 0;
137
140
  public maxBrowseContinuationPoints = 0; // 0=no limits
138
141
  private _sessionId: NodeId = new NodeId(NodeIdType.GUID, randomGuid());
139
- private readonly addressSpace: IAddressSpace;
142
+ private readonly [$addressSpace]: IAddressSpace;
140
143
  private readonly continuationPointManager: ContinuationPointManager;
141
- private readonly context: ISessionContext;
144
+ private readonly [$context]: ISessionContext;
142
145
 
143
146
  constructor(addressSpace: IAddressSpace, context?: ISessionContext) {
144
- this.addressSpace = addressSpace;
145
- this.context = context || SessionContext.defaultContext;
147
+ this[$addressSpace] = addressSpace;
148
+ this[$context] = context || SessionContext.defaultContext;
146
149
  this.continuationPointManager = new ContinuationPointManager();
147
150
  }
148
151
 
@@ -166,7 +169,7 @@ export class PseudoSession implements IBasicSession {
166
169
  browseDescription.referenceTypeId = resolveNodeId(browseDescription.referenceTypeId!);
167
170
  const _browseDescription = coerceBrowseDescription(browseDescription);
168
171
  const nodeId = resolveNodeId(_browseDescription.nodeId);
169
- const r = this.addressSpace.browseSingleNode(nodeId, _browseDescription, this.context);
172
+ const r = this[$addressSpace].browseSingleNode(nodeId, _browseDescription, this[$context]);
170
173
  results.push(r);
171
174
  }
172
175
  callack(null, results);
@@ -176,7 +179,7 @@ export class PseudoSession implements IBasicSession {
176
179
  innerBrowse(
177
180
  {
178
181
  browseAll,
179
- context: this.context,
182
+ context: this[$context],
180
183
  continuationPointManager: this.continuationPointManager,
181
184
  requestedMaxReferencesPerNode: this.requestedMaxReferencesPerNode,
182
185
  maxBrowseContinuationPoints: this.maxBrowseContinuationPoints
@@ -197,13 +200,13 @@ export class PseudoSession implements IBasicSession {
197
200
  nodesToRead = [nodesToRead as ReadValueIdOptions];
198
201
  }
199
202
  const _nodesToRead = nodesToRead as ReadValueIdOptions[];
200
- const context = this.context;
203
+ const context = this[$context];
201
204
 
202
205
  setImmediate(() => {
203
206
  async.map(
204
207
  _nodesToRead,
205
208
  (nodeToRead: ReadValueIdOptions, innerCallback: any) => {
206
- const obj = this.addressSpace.findNode(nodeToRead.nodeId!);
209
+ const obj = this[$addressSpace].findNode(nodeToRead.nodeId!);
207
210
  if (!obj || obj.nodeClass !== NodeClass.Variable || nodeToRead.attributeId !== AttributeIds.Value) {
208
211
  return innerCallback();
209
212
  }
@@ -218,11 +221,11 @@ export class PseudoSession implements IBasicSession {
218
221
  const attributeId = nodeToRead.attributeId!;
219
222
  const indexRange = nodeToRead.indexRange;
220
223
  const dataEncoding = nodeToRead.dataEncoding;
221
- const obj = this.addressSpace.findNode(nodeId);
224
+ const obj = this[$addressSpace].findNode(nodeId);
222
225
  if (!obj) {
223
226
  return new DataValue({ statusCode: StatusCodes.BadNodeIdUnknown });
224
227
  }
225
- const context = this.context;
228
+ const context = this[$context];
226
229
  const dataValue = obj.readAttribute(context, attributeId, indexRange, dataEncoding);
227
230
  return dataValue;
228
231
  });
@@ -288,8 +291,8 @@ export class PseudoSession implements IBasicSession {
288
291
  const callMethodRequest = new CallMethodRequest(methodToCall);
289
292
 
290
293
  callMethodHelper(
291
- this.context,
292
- this.addressSpace,
294
+ this[$context],
295
+ this[$addressSpace],
293
296
  callMethodRequest,
294
297
  (err: Error | null, result?: CallMethodResultOptions) => {
295
298
  let callMethodResult: CallMethodResult;
@@ -327,7 +330,7 @@ export class PseudoSession implements IBasicSession {
327
330
  browsePaths = [browsePaths as BrowsePath];
328
331
  }
329
332
  const browsePathResults = (browsePaths as BrowsePath[]).map((browsePath: BrowsePath) => {
330
- return this.addressSpace.browsePath(browsePath);
333
+ return this[$addressSpace].browsePath(browsePath);
331
334
  });
332
335
  callback!(null, isArray ? browsePathResults : browsePathResults[0]);
333
336
  }
@@ -338,14 +341,14 @@ export class PseudoSession implements IBasicSession {
338
341
  public write(nodesToWrite: WriteValueOptions[] | WriteValueOptions, callback?: ResponseCallback<any>): any {
339
342
  const isArray = nodesToWrite instanceof Array;
340
343
  const _nodesToWrite: WriteValueOptions[] = !isArray ? [nodesToWrite] : nodesToWrite;
341
- const context = this.context;
344
+ const context = this[$context];
342
345
  setImmediate(() => {
343
346
  const statusCodesPromises = _nodesToWrite.map((nodeToWrite: WriteValueOptions) => {
344
347
  assert(!!nodeToWrite.nodeId, "expecting a nodeId");
345
348
  assert(!!nodeToWrite.attributeId, "expecting a attributeId");
346
349
 
347
350
  const nodeId = nodeToWrite.nodeId!;
348
- const obj = this.addressSpace.findNode(nodeId);
351
+ const obj = this[$addressSpace].findNode(nodeId);
349
352
  if (!obj) {
350
353
  return StatusCodes.BadNodeIdUnknown;
351
354
  }
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import * as chalk from "chalk";
5
5
 
6
- import { UAReference, BaseNode, UAObject, UAVariable } from "node-opcua-address-space-base";
6
+ import { UAReference, BaseNode, UAObject, UAVariable, UAObjectType, UAVariableType } from "node-opcua-address-space-base";
7
7
  import { assert } from "node-opcua-assert";
8
8
  import { BrowseDirection, NodeClass } from "node-opcua-data-model";
9
9
  import { Enum, EnumItem } from "node-opcua-enum";
@@ -101,40 +101,42 @@ try {
101
101
  export function _handle_model_change_event(node: BaseNodeImpl): void {
102
102
  const addressSpace = node.addressSpace as AddressSpacePrivate;
103
103
  //
104
- const parent = node.parent!;
104
+ const parents = node.parent ? [node.parent] : [];
105
105
 
106
- if (parent && (parent as BaseNode).nodeVersion) {
107
- addressSpace.modelChangeTransaction(() => {
108
- let typeDefinitionNodeId = null;
109
-
110
- if (node.nodeClass === NodeClass.Object || node.nodeClass === NodeClass.Variable) {
111
- typeDefinitionNodeId = node.typeDefinitionObj.nodeId;
112
- }
113
-
114
- const modelChange1 = new ModelChangeStructureDataType({
115
- affected: node.nodeId,
116
- affectedType: typeDefinitionNodeId,
117
- verb: makeVerb("NodeAdded")
118
- });
119
- addressSpace._collectModelChange(null, modelChange1);
120
-
121
- const modelChangeSrc = new ModelChangeStructureDataType({
122
- affected: parent.nodeId,
123
- affectedType: null,
124
- verb: makeVerb("ReferenceAdded")
125
- });
126
- addressSpace._collectModelChange(null, modelChangeSrc);
106
+ const containingFolders = node.findReferencesExAsObject("Organizes", BrowseDirection.Inverse);
127
107
 
128
- // bidirectional
129
- if ((node as BaseNode).nodeVersion) {
130
- const modelChangeTgt = new ModelChangeStructureDataType({
108
+ let typeDefinitionNodeId: NodeId | null = null;
109
+ if (node.nodeClass === NodeClass.Object || node.nodeClass === NodeClass.Variable) {
110
+ typeDefinitionNodeId = node.typeDefinitionObj.nodeId;
111
+ }
112
+ for (const parent of [...parents, ...containingFolders]) {
113
+ if (parent && (parent as BaseNode).nodeVersion) {
114
+ addressSpace.modelChangeTransaction(() => {
115
+ const modelChange1 = new ModelChangeStructureDataType({
131
116
  affected: node.nodeId,
132
117
  affectedType: typeDefinitionNodeId,
118
+ verb: makeVerb("NodeAdded")
119
+ });
120
+ addressSpace._collectModelChange(null, modelChange1);
121
+
122
+ const modelChangeSrc = new ModelChangeStructureDataType({
123
+ affected: parent.nodeId,
124
+ affectedType: null,
133
125
  verb: makeVerb("ReferenceAdded")
134
126
  });
135
- addressSpace._collectModelChange(null, modelChangeTgt);
136
- }
137
- });
127
+ addressSpace._collectModelChange(null, modelChangeSrc);
128
+
129
+ // bidirectional
130
+ if ((node as BaseNode).nodeVersion) {
131
+ const modelChangeTgt = new ModelChangeStructureDataType({
132
+ affected: node.nodeId,
133
+ affectedType: typeDefinitionNodeId,
134
+ verb: makeVerb("ReferenceAdded")
135
+ });
136
+ addressSpace._collectModelChange(null, modelChangeTgt);
137
+ }
138
+ });
139
+ }
138
140
  }
139
141
  }
140
142
 
@@ -172,23 +172,8 @@ export class UAMultiStateValueDiscreteImpl<T, DT extends DataType>
172
172
  // this includes signed and unsigned integers from 8 to 64 Bit length.
173
173
 
174
174
  // istanbul ignore next
175
- if (
176
- typeof this.dataType.value !== "number" ||
177
- [
178
- DataType.UInt64,
179
- DataType.Int64,
180
- DataType.UInt32,
181
- DataType.Int32,
182
- DataType.UInt16,
183
- DataType.Int16,
184
- DataType.Byte,
185
- DataType.Byte,
186
- DataType.SByte,
187
- 26 /*Number*/
188
- ].indexOf(this.dataType.value as number) <= 0
189
- ) {
190
- throw new Error("Invalid DataType in UAMultiStateValueDiscrete =>" + this.dataType.toString());
191
- }
175
+ validateDataType(this.dataType.value);
176
+
192
177
  // find the enum value type
193
178
  install_synchronization(this);
194
179
  }
@@ -298,3 +283,22 @@ export function _addMultiStateValueDiscrete<T, DT extends DataType>(
298
283
  assert(variable.valueAsText.browseName.toString() === "ValueAsText");
299
284
  return variable;
300
285
  }
286
+
287
+ export function validateDataType(dataTypeValue: any): void {
288
+ const validTypes = [
289
+ DataType.UInt64,
290
+ DataType.Int64,
291
+ DataType.UInt32,
292
+ DataType.Int32,
293
+ DataType.UInt16,
294
+ DataType.Int16,
295
+ DataType.Byte,
296
+ DataType.Byte,
297
+ DataType.SByte,
298
+ 26 /*Number*/
299
+ ];
300
+
301
+ if (typeof dataTypeValue !== "number" || validTypes.indexOf(dataTypeValue) < 0) {
302
+ throw new Error(`Invalid DataType in UAMultiStateValueDiscrete => ${dataTypeValue.toString()}`);
303
+ }
304
+ }
@@ -670,7 +670,7 @@ export class NamespaceImpl implements NamespacePrivate {
670
670
 
671
671
  node.install_extra_properties();
672
672
 
673
- _handle_node_version(node, options);
673
+ _create_node_version_if_needed(node, options);
674
674
 
675
675
  _handle_model_change_event(node as BaseNodeImpl);
676
676
  });
@@ -1487,7 +1487,7 @@ export class NamespaceImpl implements NamespacePrivate {
1487
1487
  assert(typeof toState === "string");
1488
1488
  assert(isFinite(transitionNumber));
1489
1489
 
1490
- const fromStateNode = _component.getComponentByName(fromState, _component.nodeId.namespace);
1490
+ const fromStateNode = _component.getComponentByName(fromState);
1491
1491
 
1492
1492
  // istanbul ignore next
1493
1493
  if (!fromStateNode) {
@@ -2209,12 +2209,16 @@ export function isNonEmptyQualifiedName(browseName?: null | string | QualifiedNa
2209
2209
  return browseName.name!.length > 0;
2210
2210
  }
2211
2211
 
2212
- function _handle_node_version(node: BaseNode, options: any) {
2212
+ function _create_node_version_if_needed(node: BaseNode, options: {nodeVersion: boolean}) {
2213
2213
  assert(options);
2214
2214
  if (options.nodeVersion) {
2215
2215
  assert(node.nodeClass === NodeClass.Variable || node.nodeClass === NodeClass.Object);
2216
-
2217
- const nodeVersion = node.addressSpace.getOwnNamespace().addVariable({
2216
+ // istanbul ignore next
2217
+ if (node.getChildByName("NodeVersion")) {
2218
+ return; // already exists
2219
+ }
2220
+ const namespace = node.addressSpace.getOwnNamespace();
2221
+ const nodeVersion = namespace.addVariable({
2218
2222
  browseName: "NodeVersion",
2219
2223
  dataType: "String",
2220
2224
  propertyOf: node
@@ -446,7 +446,7 @@
446
446
 
447
447
  /**
448
448
  *
449
- *
449
+ *
450
450
  * from OPC.UA.Spec 1.02 part 4
451
451
  * 5.10.2.4 StatusCodes
452
452
  * Table 51 defines values for the operation level statusCode contained in the DataValue structure of
@@ -454,22 +454,22 @@
454
454
  *
455
455
  * Table 51 Read Operation Level Result Codes
456
456
  *
457
- * Symbolic Id Description
458
- *
459
- * BadNodeIdInvalid The syntax of the node id is not valid.
460
- * BadNodeIdUnknown The node id refers to a node that does not exist in the server address space.
461
- * BadAttributeIdInvalid BadAttributeIdInvalid The attribute is not supported for the specified node.
462
- * BadIndexRangeInvalid The syntax of the index range parameter is invalid.
463
- * BadIndexRangeNoData No data exists within the range of indexes specified.
464
- * BadDataEncodingInvalid The data encoding is invalid.
465
- * This result is used if no dataEncoding can be applied because an Attribute other
466
- * than Value was requested or the DataType of the Value Attribute is not a subtype
467
- * of the Structure DataType.
468
- * BadDataEncodingUnsupported The server does not support the requested data encoding for the node.
469
- * This result is used if a dataEncoding can be applied but the passed data encoding
470
- * is not known to the Server.
471
- * BadNotReadable The access level does not allow reading or subscribing to the Node.
472
- * BadUserAccessDenied User does not have permission to perform the requested operation. (table 165)
457
+ * | Symbolic Id | Description
458
+ * |-----------------------------|---------------------------------------------------------------------------------------------|
459
+ * |BadNodeIdInvalid | The syntax of the node id is not valid.|
460
+ * |BadNodeIdUnknown |The node id refers to a node that does not exist in the server address space.|
461
+ * |BadAttributeIdInvalid | BadAttributeIdInvalid The attribute is not supported for the specified node.|
462
+ * |BadIndexRangeInvalid | The syntax of the index range parameter is invalid.|
463
+ * |BadIndexRangeNoData | No data exists within the range of indexes specified.|
464
+ * |BadDataEncodingInvalid | The data encoding is invalid.|
465
+ * | | This result is used if no dataEncoding can be applied because an Attribute other|
466
+ * | | than Value was requested or the DataType of the Value Attribute is not a subtype|
467
+ * | | of the Structure DataType.|
468
+ * |BadDataEncodingUnsupported | The server does not support the requested data encoding for the node. |
469
+ * | | This result is used if a dataEncoding can be applied but the passed data encoding |
470
+ * | | is not known to the Server. |
471
+ * |BadNotReadable | The access level does not allow reading or subscribing to the Node.|
472
+ * |BadUserAccessDenied | User does not have permission to perform the requested operation. (table 165)|
473
473
  */
474
474
  public readValue(
475
475
  context?: ISessionContext | null,
@@ -589,7 +589,7 @@
589
589
  }
590
590
 
591
591
  if (this.$dataValue.serverTimestamp && oldestDate.getTime() <= this.$dataValue.serverTimestamp!.getTime()) {
592
- const dataValue = this.readValue();
592
+ const dataValue = this.readValue().clone();
593
593
  dataValue.serverTimestamp = oldestDate;
594
594
  dataValue.serverPicoseconds = 0;
595
595
  return callback(null, dataValue);
@@ -606,7 +606,7 @@
606
606
  );
607
607
  dataValue = { statusCode: StatusCodes.BadNoDataAvailable };
608
608
  }
609
- if (dataValue !== this.$dataValue) {
609
+ if (dataValue && dataValue !== this.$dataValue) {
610
610
  this._internal_set_dataValue(coerceDataValue(dataValue), null);
611
611
  }
612
612
  callback(err, this.$dataValue);