node-opcua-address-space 2.87.0 → 2.88.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.
@@ -23,7 +23,7 @@ const errorLog = make_errorLog(__filename);
23
23
  *
24
24
  */
25
25
 
26
- function getExtObjArrayNodeValue<T extends ExtensionObject>(this: UADynamicVariableArray<T>) {
26
+ function getExtObjArrayNodeValue<T extends ExtensionObject>(this: UADynamicVariableArray<T>) {
27
27
  return new Variant({
28
28
  arrayType: VariantArrayType.Array,
29
29
  dataType: DataType.ExtensionObject,
@@ -38,7 +38,7 @@ function removeElementByIndex<T extends ExtensionObject>(uaArrayVariableNode: UA
38
38
 
39
39
  const addressSpace = uaArrayVariableNode.addressSpace;
40
40
  const extObj = _array[elementIndex];
41
- const browseName = uaArrayVariableNode.$$getElementBrowseName(extObj);
41
+ const browseName = uaArrayVariableNode.$$getElementBrowseName(extObj, elementIndex);
42
42
 
43
43
  // remove element from global array (inefficient)
44
44
  uaArrayVariableNode.$$extensionObjectArray.splice(elementIndex, 1);
@@ -68,14 +68,6 @@ function removeElementByIndex<T extends ExtensionObject>(uaArrayVariableNode: UA
68
68
  /**
69
69
  *
70
70
  * create a node Variable that contains a array of ExtensionObject of a given type
71
- * @method createExtObjArrayNode
72
- * @param parentFolder
73
- * @param options
74
- * @param options.browseName
75
- * @param options.complexVariableType
76
- * @param options.variableType the type of Extension objects stored in the array.
77
- * @param options.indexPropertyName
78
- * @return {Object|UAVariable}
79
71
  */
80
72
  export function createExtObjArrayNode<T extends ExtensionObject>(parentFolder: UAObject, options: any): UADynamicVariableArray<T> {
81
73
  assert(typeof options.variableType === "string");
@@ -126,7 +118,9 @@ export function createExtObjArrayNode<T extends ExtensionObject>(parentFolder: U
126
118
 
127
119
  return uaArrayVariableNode;
128
120
  }
129
- function _getElementBrowseName<T extends ExtensionObject>(this: UADynamicVariableArray<T>, extObj: ExtensionObject) {
121
+
122
+ function _getElementBrowseName<T extends ExtensionObject>
123
+ (this: UADynamicVariableArray<T>, extObj: ExtensionObject, index: number | number[]) {
130
124
  const indexPropertyName1 = this.$$indexPropertyName;
131
125
 
132
126
  if (!Object.prototype.hasOwnProperty.call(extObj, indexPropertyName1)) {
@@ -137,13 +131,7 @@ function _getElementBrowseName<T extends ExtensionObject>(this: UADynamicVariabl
137
131
  const browseName = (extObj as any)[indexPropertyName1].toString();
138
132
  return browseName;
139
133
  };
140
- /**
141
- * @method bindExtObjArrayNode
142
- * @param uaArrayVariableNode
143
- * @param variableTypeNodeId
144
- * @param indexPropertyName
145
- * @return
146
- */
134
+
147
135
  export function bindExtObjArrayNode<T extends ExtensionObject>(
148
136
  uaArrayVariableNode: UADynamicVariableArray<T>,
149
137
  variableTypeNodeId: string | NodeId,
@@ -155,26 +143,23 @@ export function bindExtObjArrayNode<T extends ExtensionObject>(
155
143
  const addressSpace = uaArrayVariableNode.addressSpace;
156
144
 
157
145
  const variableType = addressSpace.findVariableType(variableTypeNodeId);
158
-
159
146
  // istanbul ignore next
160
147
  if (!variableType || variableType.nodeId.isEmpty()) {
161
148
  throw new Error("Cannot find VariableType " + variableTypeNodeId.toString());
162
149
  }
163
150
 
164
151
  const structure = addressSpace.findDataType("Structure");
165
-
166
152
  // istanbul ignore next
167
153
  if (!structure) {
168
154
  throw new Error("Structure Type not found: please check your nodeset file");
169
155
  }
170
156
 
171
157
  let dataType = addressSpace.findDataType(variableType.dataType);
172
-
173
158
  // istanbul ignore next
174
159
  if (!dataType) {
175
160
  throw new Error("Cannot find DataType " + variableType.dataType.toString());
176
161
  }
177
-
162
+
178
163
  assert(dataType.isSupertypeOf(structure), "expecting a structure (= ExtensionObject) here ");
179
164
 
180
165
  assert(!uaArrayVariableNode.$$variableType, "uaArrayVariableNode has already been bound !");
@@ -197,7 +182,6 @@ export function bindExtObjArrayNode<T extends ExtensionObject>(
197
182
  };
198
183
  // bind the readonly
199
184
  uaArrayVariableNode.bindVariable(bindOptions, true);
200
-
201
185
  return uaArrayVariableNode;
202
186
  }
203
187
 
@@ -208,20 +192,9 @@ export function bindExtObjArrayNode<T extends ExtensionObject>(
208
192
  * @param uaArrayVariableNode {UAVariable}
209
193
  * @return {UAVariable}
210
194
  *
211
- * @method addElement
212
- * add a new element in a ExtensionObject Array variable
213
- * @param nodeVariable a variable already exposing an extension objects
214
- * @param uaArrayVariableNode {UAVariable}
215
- * @return {UAVariable}
216
- *
217
- * @method addElement
218
- * add a new element in a ExtensionObject Array variable
219
- * @param constructor constructor of the extension object to create
220
- * @param uaArrayVariableNode {UAVariable}
221
- * @return {UAVariable}
222
195
  */
223
196
  export function addElement<T extends ExtensionObject>(
224
- options: any /* ExtensionObjectConstructor | ExtensionObject | UAVariable*/,
197
+ options: UAVariableImpl | ExtensionObject | Record<string, unknown>,
225
198
  uaArrayVariableNode: UADynamicVariableArray<T>
226
199
  ): UAVariable {
227
200
  assert(uaArrayVariableNode, " must provide an UAVariable containing the array");
@@ -256,19 +229,20 @@ export function addElement<T extends ExtensionObject>(
256
229
  });
257
230
  // xx elVar.bindExtensionObject();
258
231
  } else {
259
- if (options instanceof Constructor) {
232
+ if (options instanceof ExtensionObject) {
260
233
  // extension object has already been created
261
234
  extensionObject = options as T;
262
235
  } else {
263
236
  extensionObject = addressSpace.constructExtensionObject(uaArrayVariableNode.$$dataType, options) as T;
264
237
  }
265
- browseName = uaArrayVariableNode.$$getElementBrowseName(extensionObject);
238
+ const index = uaArrayVariableNode.$$extensionObjectArray?.length || 0;
239
+ browseName = uaArrayVariableNode.$$getElementBrowseName(extensionObject, index);
266
240
  elVar = uaArrayVariableNode.$$variableType.instantiate({
267
241
  browseName,
268
242
  componentOf: uaArrayVariableNode.nodeId,
269
243
  value: { dataType: DataType.ExtensionObject, value: extensionObject }
270
244
  }) as UAVariableImpl;
271
- elVar.bindExtensionObject(extensionObject, { force: true });
245
+ elVar.bindExtensionObject(extensionObject, { force: true });
272
246
  elVar.$extensionObject = extensionObject;
273
247
  }
274
248
 
@@ -279,19 +253,6 @@ export function addElement<T extends ExtensionObject>(
279
253
  }
280
254
 
281
255
  /**
282
- *
283
- * @method removeElement
284
- * @param uaArrayVariableNode {UAVariable}
285
- * @param element {number} index of element to remove in array
286
- *
287
- *
288
- * @method removeElement
289
- * @param uaArrayVariableNode {UAVariable}
290
- * @param element {UAVariable} node of element to remove in array
291
- *
292
- * @method removeElement
293
- * @param uaArrayVariableNode {UAVariable}
294
- * @param element {ExtensionObject} extension object of the node of element to remove in array
295
256
  *
296
257
  */
297
258
  export function removeElement<T extends ExtensionObject>(
@@ -305,7 +266,7 @@ export function removeElement<T extends ExtensionObject>(
305
266
  if (_array.length === 0) {
306
267
  throw new Error(" cannot remove an element from an empty array ");
307
268
  }
308
-
269
+
309
270
  let elementIndex = -1;
310
271
 
311
272
  if (typeof element === "number") {
@@ -316,7 +277,7 @@ export function removeElement<T extends ExtensionObject>(
316
277
  // find element by name
317
278
  const browseNameToFind = element.browseName.name!.toString();
318
279
  elementIndex = _array.findIndex((obj: any, i: number) => {
319
- const browseName = uaArrayVariableNode.$$getElementBrowseName(obj).toString();
280
+ const browseName = uaArrayVariableNode.$$getElementBrowseName(obj, elementIndex).toString();
320
281
  return browseName === browseNameToFind;
321
282
  });
322
283
  } else if (typeof element === "function") {
@@ -327,7 +288,7 @@ export function removeElement<T extends ExtensionObject>(
327
288
  assert(_array[0].constructor.name === (element as any).constructor.name, "element must match");
328
289
  elementIndex = _array.findIndex((x: any) => x === element);
329
290
  }
330
-
291
+
331
292
  // istanbul ignore next
332
293
  if (elementIndex < 0) {
333
294
  throw new Error("removeElement: cannot find element matching " + element.toString());
@@ -0,0 +1,52 @@
1
+
2
+
3
+
4
+
5
+ export class IndexIterator {
6
+
7
+ public current: number[] | null = null;
8
+ constructor(private limits: number[]) {
9
+ this.reset();
10
+ }
11
+ public reset() {
12
+ this.current = [];
13
+ for (let i = 0; i < this.limits.length; i++) {
14
+ this.current[i] = 0;
15
+ }
16
+ }
17
+ public increment() {
18
+ if (!this.current) return;
19
+
20
+
21
+ const increase = (n: number): boolean => {
22
+ if (n < 0) {
23
+ return false;
24
+ }
25
+ if (!this.current) return false;
26
+ if (this.current[n] + 1 >= this.limits[n]) {
27
+ if (n==0) {
28
+ this.current = null;
29
+ return false;
30
+ }
31
+ this.current[n] = 0;
32
+ return increase(n - 1);
33
+ }
34
+ this.current[n] = this.current[n] + 1;
35
+ return true;
36
+ }
37
+ const n = this.limits.length - 1;
38
+ if (!increase(n)) {
39
+ this.current = null;
40
+ }
41
+ }
42
+ public next(): number[] {
43
+ if (!this.current) {
44
+ throw new Error("Outof bond");
45
+ }
46
+ const r = [... this.current];
47
+ this.increment();
48
+ return r;
49
+ }
50
+
51
+
52
+ }
@@ -28,7 +28,8 @@ import {
28
28
  AccessLevelFlag,
29
29
  makeAccessLevelFlag,
30
30
  AttributeIds,
31
- isDataEncoding
31
+ isDataEncoding,
32
+ QualifiedName
32
33
  } from "node-opcua-data-model";
33
34
  import { extractRange, sameDataValue, DataValue, DataValueLike, DataValueT } from "node-opcua-data-value";
34
35
  import { coerceClock, getCurrentClock, PreciseClock } from "node-opcua-date-time";
@@ -87,6 +88,8 @@ import {
87
88
  propagateTouchValueUpward,
88
89
  setExtensionObjectValue,
89
90
  _bindExtensionObject,
91
+ _bindExtensionObjectArray,
92
+ _bindExtensionObjectMatrix,
90
93
  _installExtensionObjectBindingOnProperties,
91
94
  _setExtensionObject,
92
95
  _touchValue
@@ -1471,24 +1474,41 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1471
1474
  * @return {ExtensionObject}
1472
1475
  */
1473
1476
  public bindExtensionObjectScalar(
1474
- optionalExtensionObject: ExtensionObject,
1477
+ optionalExtensionObject?: ExtensionObject,
1475
1478
  options?: BindExtensionObjectOptions
1476
1479
  ): ExtensionObject | null {
1477
- return this.bindExtensionObject(optionalExtensionObject, options) as ExtensionObject | null;
1480
+ return _bindExtensionObject(this, optionalExtensionObject, options) as ExtensionObject;
1478
1481
  }
1479
1482
 
1480
1483
  public bindExtensionObjectArray(
1481
- optionalExtensionObject: ExtensionObject[],
1484
+ optionalExtensionObject?: ExtensionObject[],
1482
1485
  options?: BindExtensionObjectOptions
1483
1486
  ): ExtensionObject[] | null {
1484
- return this.bindExtensionObject(optionalExtensionObject, options) as ExtensionObject[] | null;
1487
+ assert(this.valueRank === 1, "expecting a Array variable here");
1488
+ return _bindExtensionObjectArray(this, optionalExtensionObject, options) as ExtensionObject[];
1485
1489
  }
1486
1490
 
1487
1491
  public bindExtensionObject(
1488
1492
  optionalExtensionObject?: ExtensionObject | ExtensionObject[],
1489
1493
  options?: BindExtensionObjectOptions
1490
1494
  ): ExtensionObject | ExtensionObject[] | null {
1491
- return _bindExtensionObject(this, optionalExtensionObject, options);
1495
+ if (optionalExtensionObject) {
1496
+ if (optionalExtensionObject instanceof Array) {
1497
+ return this.bindExtensionObjectArray(optionalExtensionObject, options);
1498
+ } else {
1499
+ return this.bindExtensionObjectScalar(optionalExtensionObject, options);
1500
+ }
1501
+ }
1502
+ assert(optionalExtensionObject === undefined);
1503
+ if (this.valueRank === -1) {
1504
+ return this.bindExtensionObjectScalar(undefined, options);
1505
+ } else if (this.valueRank === 1) {
1506
+ return this.bindExtensionObjectArray(undefined, options);
1507
+ } else if (this.valueRank > 1) {
1508
+ return _bindExtensionObjectMatrix(this, undefined, options);
1509
+ }
1510
+ // unsupported case ...
1511
+ return null;
1492
1512
  }
1493
1513
 
1494
1514
  public updateExtensionObjectPartial(partialExtensionObject?: { [key: string]: any }): ExtensionObject {
@@ -1830,14 +1850,15 @@ UAVariableImpl.prototype.writeAttribute = thenify.withCallback(UAVariableImpl.pr
1830
1850
  UAVariableImpl.prototype.historyRead = thenify.withCallback(UAVariableImpl.prototype.historyRead);
1831
1851
  UAVariableImpl.prototype.readValueAsync = thenify.withCallback(UAVariableImpl.prototype.readValueAsync);
1832
1852
 
1833
- export interface UAVariableImpl {
1834
- $$variableType?: any;
1835
- $$dataType?: any;
1836
- $$getElementBrowseName: any;
1837
- $$extensionObjectArray: any;
1838
- $$indexPropertyName: any;
1853
+ export interface UAVariableImplExtArray {
1854
+ $$variableType?: UAVariableType;
1855
+ $$dataType: UADataType;
1856
+ $$getElementBrowseName: (extObject: ExtensionObject, index: number | number[]) => QualifiedName;
1857
+ $$extensionObjectArray: ExtensionObject[];
1858
+ $$indexPropertyName: string;
1859
+ }
1860
+ export interface UAVariableImpl extends UAVariableImplExtArray {
1839
1861
  }
1840
-
1841
1862
  function check_valid_array(dataType: DataType, array: any): boolean {
1842
1863
  if (Array.isArray(array)) {
1843
1864
  return true;
@@ -1,12 +1,12 @@
1
1
  import * as chalk from "chalk";
2
2
  import assert from "node-opcua-assert";
3
- import { BindExtensionObjectOptions, UADataType, UAVariable, UAVariableType } from "node-opcua-address-space-base";
4
- import { NodeClass } from "node-opcua-data-model";
5
- import { getCurrentClock, PreciseClock } from "node-opcua-date-time";
3
+ import { BindExtensionObjectOptions, UADataType, UADynamicVariableArray, UAVariable, UAVariableType } from "node-opcua-address-space-base";
4
+ import { coerceQualifiedName, NodeClass } from "node-opcua-data-model";
5
+ import { getCurrentClock, PreciseClock, DateWithPicoseconds, coerceClock } from "node-opcua-date-time";
6
6
  import { DataValue } from "node-opcua-data-value";
7
7
  import { make_debugLog, make_warningLog, checkDebugFlag, make_errorLog } from "node-opcua-debug";
8
8
  import { ExtensionObject } from "node-opcua-extension-object";
9
- import { NodeId } from "node-opcua-nodeid";
9
+ import { coerceNodeId, NodeId, NodeIdType } from "node-opcua-nodeid";
10
10
  import { StatusCodes, CallbackT, StatusCode } from "node-opcua-status-code";
11
11
  import { StructureField } from "node-opcua-types";
12
12
  import { lowerFirstLetter } from "node-opcua-utils";
@@ -15,6 +15,9 @@ import { DataType, Variant, VariantLike, VariantArrayType } from "node-opcua-var
15
15
  import { valueRankToString } from "./base_node_private";
16
16
  import { UAVariableImpl } from "./ua_variable_impl";
17
17
  import { UADataTypeImpl } from "./ua_data_type_impl";
18
+ import { bindExtObjArrayNode } from "./extension_object_array_node";
19
+ import { IndexIterator } from "./idx_iterator";
20
+ import { DateTime } from "node-opcua-basic-types";
18
21
 
19
22
  const doDebug = checkDebugFlag(__filename);
20
23
  const debugLog = make_debugLog(__filename);
@@ -114,7 +117,7 @@ function propagateTouchValueDownward(self: UAVariableImpl, now: PreciseClock): v
114
117
  }
115
118
  }
116
119
 
117
- export function _setExtensionObject(self: UAVariableImpl, ext: ExtensionObject | ExtensionObject[]): void {
120
+ export function _setExtensionObject(self: UAVariableImpl, ext: ExtensionObject | ExtensionObject[], sourceTimestamp?: PreciseClock): void {
118
121
  // assert(!(ext as any).$isProxy, "internal error ! ExtensionObject has already been proxied !");
119
122
  if (Array.isArray(ext)) {
120
123
  assert(self.valueRank === 1, "Only Array is supported for the time being");
@@ -133,7 +136,7 @@ export function _setExtensionObject(self: UAVariableImpl, ext: ExtensionObject |
133
136
  self.$dataValue.statusCode = StatusCodes.Good;
134
137
  }
135
138
 
136
- const now = getCurrentClock();
139
+ const now = sourceTimestamp || getCurrentClock();
137
140
  propagateTouchValueUpward(self, now);
138
141
  propagateTouchValueDownward(self, now);
139
142
  }
@@ -193,7 +196,10 @@ function getOrCreateProperty(
193
196
  browseName: { namespaceIndex: structureNamespace, name: field.name!.toString() },
194
197
  componentOf: variableNode,
195
198
  dataType: field.dataType,
196
- minimumSamplingInterval: variableNode.minimumSamplingInterval
199
+ minimumSamplingInterval: variableNode.minimumSamplingInterval,
200
+ accessLevel: variableNode.accessLevel,
201
+ accessRestrictions: variableNode.accessRestrictions,
202
+ rolePermissions: variableNode.rolePermissions
197
203
  }) as UAVariableImpl;
198
204
  assert(property.minimumSamplingInterval === variableNode.minimumSamplingInterval);
199
205
  }
@@ -226,17 +232,46 @@ function bindProperty(variableNode: UAVariableImpl, propertyNode: UAVariableImpl
226
232
  return new DataValue(propertyNode.$dataValue);
227
233
  },
228
234
  timestamped_set: (_dataValue: DataValue, callback: CallbackT<StatusCode>) => {
229
- callback(null, StatusCodes.BadNotWritable);
235
+
236
+ propertyNode.setValueFromSource(_dataValue.value, _dataValue.statusCode, _dataValue.sourceTimestamp || new Date());
237
+ // callback(null, StatusCodes.BadNotWritable);
238
+ callback(null, StatusCodes.Good);
230
239
  }
231
240
  },
232
241
  true
233
242
  );
234
243
  }
235
244
 
245
+
246
+ function _initial_setup(variableNode: UAVariableImpl) {
247
+ const dataValue = variableNode.readValue();
248
+ const extObj = dataValue.value.value;
249
+ if (extObj instanceof ExtensionObject) {
250
+ variableNode.bindExtensionObject(extObj, { createMissingProp: true, force: true });
251
+ } else if (extObj instanceof Array) {
252
+ if (dataValue.value.arrayType === VariantArrayType.Array) {
253
+ variableNode.bindExtensionObjectArray(extObj, { createMissingProp: true, force: true });
254
+ } else if (dataValue.value.arrayType === VariantArrayType.Matrix) {
255
+ _bindExtensionObjectMatrix(variableNode, extObj, { createMissingProp: true, force: true });
256
+ } else {
257
+ throw new Error("Internal Error, unexpected case");
258
+ }
259
+ } else {
260
+ const msg = `variableNode ${variableNode.browseName.toString()} doesn't have $extensionObject property`;
261
+ errorLog(msg);
262
+ errorLog(dataValue.toString());
263
+ throw new Error(msg);
264
+ }
265
+ }
236
266
  export function _installExtensionObjectBindingOnProperties(
237
267
  variableNode: UAVariableImpl,
238
268
  options: BindExtensionObjectOptions
239
269
  ): void {
270
+
271
+ if (!variableNode.$extensionObject) {
272
+ _initial_setup(variableNode);
273
+ return;
274
+ }
240
275
  const addressSpace = variableNode.addressSpace;
241
276
  const dt = variableNode.getDataTypeNode();
242
277
  const definition = dt.getStructureDefinition();
@@ -256,7 +291,12 @@ export function _installExtensionObjectBindingOnProperties(
256
291
  continue;
257
292
  }
258
293
  propertyNode.$dataValue.statusCode = StatusCodes.Good;
259
- propertyNode.touchValue();
294
+ propertyNode.$dataValue.sourceTimestamp = variableNode.$dataValue.sourceTimestamp;
295
+ propertyNode.$dataValue.sourcePicoseconds = variableNode.$dataValue.sourcePicoseconds;
296
+ propertyNode.$dataValue.serverTimestamp = variableNode.$dataValue.serverTimestamp;
297
+ propertyNode.$dataValue.serverPicoseconds = variableNode.$dataValue.serverPicoseconds;
298
+
299
+ //xx propertyNode.touchValue();
260
300
 
261
301
  const basicDataType = addressSpace.findCorrespondingBasicDataType(field.dataType);
262
302
 
@@ -322,36 +362,43 @@ export function _installExtensionObjectBindingOnProperties(
322
362
  }
323
363
  }
324
364
 
325
- // eslint-disable-next-line complexity
326
- export function _bindExtensionObject(
327
- self: UAVariableImpl,
328
- optionalExtensionObject?: ExtensionObject | ExtensionObject[],
329
- options?: BindExtensionObjectOptions
330
- ): ExtensionObject | ExtensionObject[] | null {
331
- options = options || { createMissingProp: false };
332
-
333
-
334
- const addressSpace = self.addressSpace;
365
+ function isVariableContainingExtensionObject(uaVariable: UAVariableImpl): boolean {
366
+ const addressSpace = uaVariable.addressSpace;
335
367
  const structure = addressSpace.findDataType("Structure");
336
- let extensionObject_;
337
368
 
338
369
  if (!structure) {
339
370
  // the addressSpace is limited and doesn't provide extension object
340
371
  // bindExtensionObject cannot be performed and shall finish here.
341
- return null;
372
+ return false;
342
373
  }
343
374
 
344
375
  assert(structure.browseName.toString() === "Structure", "expecting DataType Structure to be in IAddressSpace");
345
376
 
346
- const dt = self.getDataTypeNode() as UADataTypeImpl;
377
+ const dt = uaVariable.getDataTypeNode() as UADataTypeImpl;
347
378
  if (!dt.isSupertypeOf(structure)) {
379
+ return false;
380
+ }
381
+ return true;
382
+
383
+ }
384
+ // eslint-disable-next-line complexity
385
+ export function _bindExtensionObject(
386
+ self: UAVariableImpl,
387
+ optionalExtensionObject?: ExtensionObject | ExtensionObject[],
388
+ options?: BindExtensionObjectOptions
389
+ ): ExtensionObject | ExtensionObject[] | null {
390
+ options = options || { createMissingProp: false };
391
+
392
+ if (!isVariableContainingExtensionObject(self)) {
348
393
  return null;
349
394
  }
395
+ const addressSpace = self.addressSpace;
396
+ let extensionObject_;
350
397
 
351
- if (self.valueRank !== -1 && self.valueRank !==1) {
398
+ if (self.valueRank !== -1 && self.valueRank !== 1) {
352
399
  throw new Error("Cannot bind an extension object here, valueRank must be scalar (-1) or one-dimensional (1)");
353
400
  }
354
-
401
+
355
402
  // istanbul ignore next
356
403
  if (doDebug) {
357
404
  debugLog(" ------------------------------ binding ", self.browseName.toString(), self.nodeId.toString());
@@ -365,6 +412,7 @@ export function _bindExtensionObject(
365
412
  ) {
366
413
  const parentDataType = (self.parent as UAVariable | UAVariableType).dataType;
367
414
  const dataTypeNode = addressSpace.findNode(parentDataType) as UADataType;
415
+ const structure = addressSpace.findDataType("Structure")!;
368
416
  // istanbul ignore next
369
417
  if (dataTypeNode && dataTypeNode.isSupertypeOf(structure)) {
370
418
  // warningLog(
@@ -392,9 +440,9 @@ export function _bindExtensionObject(
392
440
  warningLog(self.$extensionObject?.toString());
393
441
  throw new Error(
394
442
  "bindExtensionObject: $extensionObject is incorrect: we are expecting a " +
395
- self.dataType.toString({ addressSpace: self.addressSpace }) +
396
- " but we got a " +
397
- self.$extensionObject?.constructor.name
443
+ self.dataType.toString({ addressSpace: self.addressSpace }) +
444
+ " but we got a " +
445
+ self.$extensionObject?.constructor.name
398
446
  );
399
447
  }
400
448
  return self.$extensionObject;
@@ -422,6 +470,10 @@ export function _bindExtensionObject(
422
470
  return self.$extensionObject;
423
471
 
424
472
  function innerBindExtensionObject() {
473
+ const makePreciseClock = (sourceTimestamp: DateTime, picoseconds?: number): PreciseClock =>
474
+ ({ timestamp: sourceTimestamp as DateWithPicoseconds, picoseconds: picoseconds || 0 });
475
+
476
+
425
477
  if (s.value && (s.value.dataType === DataType.Null || (s.value.dataType === DataType.ExtensionObject && !s.value.value))) {
426
478
  if (self.valueRank === -1 /** Scalar */) {
427
479
  // create a structure and bind it
@@ -439,13 +491,16 @@ export function _bindExtensionObject(
439
491
  if (!self.checkExtensionObjectIsCorrect(ext)) {
440
492
  return callback(null, StatusCodes.BadInvalidArgument);
441
493
  }
442
- _setExtensionObject(self, ext);
494
+ const sourceTime = coerceClock(dataValue.sourceTimestamp, dataValue.sourcePicoseconds);
495
+ _setExtensionObject(self, ext, sourceTime);
443
496
  callback(null, StatusCodes.Good);
444
497
  }
445
498
  },
446
499
  true
447
500
  );
448
- _setExtensionObject(self, extensionObject_);
501
+ const sourceTime = coerceClock(self.$dataValue.sourceTimestamp, self.$dataValue.sourcePicoseconds);
502
+ _setExtensionObject(self, extensionObject_, sourceTime);
503
+
449
504
  } else if (self.valueRank === 1 /** Array */) {
450
505
  // create a structure and bind it
451
506
 
@@ -462,17 +517,19 @@ export function _bindExtensionObject(
462
517
  return d;
463
518
  },
464
519
  timestamped_set(dataValue: DataValue, callback: CallbackT<StatusCode>) {
465
- const ext = dataValue.value.value;
466
- if (!self.checkExtensionObjectIsCorrect(ext)) {
520
+ const extensionObjectArray = dataValue.value.value;
521
+ if (!self.checkExtensionObjectIsCorrect(extensionObjectArray)) {
467
522
  return callback(null, StatusCodes.BadInvalidArgument);
468
523
  }
469
- _setExtensionObject(self, ext);
524
+ const sourceTime = coerceClock(dataValue.sourceTimestamp, dataValue.sourcePicoseconds);
525
+ _setExtensionObject(self, extensionObjectArray, sourceTime);
470
526
  callback(null, StatusCodes.Good);
471
527
  }
472
528
  },
473
529
  true
474
530
  );
475
- _setExtensionObject(self, extensionObject_);
531
+ const sourceTime = coerceClock(self.$dataValue.sourceTimestamp, self.$dataValue.sourcePicoseconds);
532
+ _setExtensionObject(self, extensionObject_, sourceTime);
476
533
  } else {
477
534
  errorLog(self.toString());
478
535
  errorLog("Unsupported case ! valueRank= ", self.valueRank);
@@ -488,6 +545,102 @@ export function _bindExtensionObject(
488
545
  }
489
546
  }
490
547
 
548
+ const getIndexAsText = (index: number | number[]): string => {
549
+ if (typeof index === "number")
550
+ return `${index}`;
551
+ return `${index.map((a) => a.toString()).join(",")}`;
552
+ }
553
+ const composeBrowseNameAndNodeId = (uaVariable: UAVariable, indexes: number[]) => {
554
+ const iAsText = getIndexAsText(indexes);
555
+ const browseName = coerceQualifiedName(iAsText);
556
+ let nodeId: NodeId | undefined;
557
+ if (uaVariable.nodeId.identifierType === NodeIdType.STRING) {
558
+ nodeId = new NodeId(NodeIdType.STRING, uaVariable.nodeId.value as string + `[${iAsText}]`, uaVariable.nodeId.namespace);
559
+ }
560
+ return { browseName, nodeId };
561
+ }
562
+
563
+ export function _bindExtensionObjectArray(
564
+ uaVariable: UAVariableImpl,
565
+ optionalExtensionObjectArray?: ExtensionObject[],
566
+ options?: BindExtensionObjectOptions
567
+ ): ExtensionObject[] {
568
+ return _bindExtensionObjectMatrix(uaVariable, optionalExtensionObjectArray, options);
569
+ }
570
+ export function _bindExtensionObjectMatrix(
571
+ uaVariable: UAVariableImpl,
572
+ optionalExtensionObjectArray?: ExtensionObject[],
573
+ options?: BindExtensionObjectOptions
574
+ ): ExtensionObject[] {
575
+
576
+ if (uaVariable.valueRank < 1) {
577
+ throw new Error("Variable must be a MultiDimensional array");
578
+ }
579
+ const arrayDimensions = uaVariable.arrayDimensions || [];
580
+
581
+ if (!isVariableContainingExtensionObject(uaVariable)) {
582
+ return [];
583
+ }
584
+ if ((arrayDimensions.length === 0 || arrayDimensions.length === 1 && arrayDimensions[0] === 0) && optionalExtensionObjectArray) {
585
+ arrayDimensions[0] = optionalExtensionObjectArray.length;
586
+ }
587
+
588
+ const totalLength = arrayDimensions.reduce((p, c) => p * c, 1);
589
+
590
+ /** */
591
+ const addressSpace = uaVariable.addressSpace;
592
+ if (optionalExtensionObjectArray) {
593
+ if (optionalExtensionObjectArray.length !== totalLength) {
594
+ throw new Error(`optionalExtensionObjectArray must have the expected number of element matching ${arrayDimensions}`)
595
+ }
596
+ }
597
+ if (!optionalExtensionObjectArray) {
598
+ optionalExtensionObjectArray = [];
599
+ for (let i = 0; i < totalLength; i++) {
600
+ optionalExtensionObjectArray[i] = addressSpace.constructExtensionObject(uaVariable.dataType, {});;
601
+ }
602
+ }
603
+ uaVariable.$$extensionObjectArray = optionalExtensionObjectArray;
604
+
605
+ // uaVariable.$$getElementBrowseName = getElementBrowseNameByIndex;
606
+
607
+ uaVariable.bindVariable({
608
+ get: () => new Variant({
609
+ arrayType: VariantArrayType.Array,
610
+ dataType: DataType.ExtensionObject,
611
+ value: uaVariable.$$extensionObjectArray
612
+ })
613
+ }, true);
614
+ const namespace = uaVariable.namespace;
615
+ const indexIterator = new IndexIterator(arrayDimensions);
616
+ for (let i = 0; i < totalLength; i++) {
617
+
618
+ const index = indexIterator.next();
619
+
620
+ const { browseName, nodeId } = composeBrowseNameAndNodeId(uaVariable, index);
621
+
622
+ const uaElement = namespace.addVariable({
623
+ browseName,
624
+ nodeId,
625
+ componentOf: uaVariable,
626
+ dataType: uaVariable.dataType,
627
+ valueRank: -1,
628
+ accessLevel: uaVariable.userAccessLevel
629
+ });
630
+ {
631
+ const capturedIndex = i;
632
+ uaElement.bindVariable({
633
+ get: () => new Variant({
634
+ dataType: DataType.ExtensionObject,
635
+ arrayType: VariantArrayType.Scalar,
636
+ value: uaVariable.$$extensionObjectArray[capturedIndex]
637
+ })
638
+ })
639
+ }
640
+ }
641
+ return uaVariable.$$extensionObjectArray;
642
+ }
643
+
491
644
  export function extractPartialData(path: string | string[], extensionObject: ExtensionObject) {
492
645
  let name;
493
646
  if (typeof path === "string") {