node-opcua-address-space 2.69.0 → 2.70.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 (35) hide show
  1. package/dist/source/helpers/argument_list.d.ts +4 -0
  2. package/dist/source/helpers/argument_list.js +50 -12
  3. package/dist/source/helpers/argument_list.js.map +1 -1
  4. package/dist/source/index.d.ts +3 -2
  5. package/dist/source/index.js +3 -2
  6. package/dist/source/index.js.map +1 -1
  7. package/dist/source/loader/load_nodeset2.js +123 -94
  8. package/dist/source/loader/load_nodeset2.js.map +1 -1
  9. package/dist/source/loader/make_xml_extension_object_parser.d.ts +5 -0
  10. package/dist/source/loader/make_xml_extension_object_parser.js +25 -1
  11. package/dist/source/loader/make_xml_extension_object_parser.js.map +1 -1
  12. package/dist/src/data_access/add_dataItem_stuff.js +2 -2
  13. package/dist/src/data_access/add_dataItem_stuff.js.map +1 -1
  14. package/dist/src/data_access/ua_multistate_value_discrete.js +1 -0
  15. package/dist/src/data_access/ua_multistate_value_discrete.js.map +1 -1
  16. package/dist/src/namespace_impl.d.ts +1 -15
  17. package/dist/src/namespace_impl.js +1 -28
  18. package/dist/src/namespace_impl.js.map +1 -1
  19. package/dist/src/ua_variable_impl.d.ts +3 -1
  20. package/dist/src/ua_variable_impl.js +77 -16
  21. package/dist/src/ua_variable_impl.js.map +1 -1
  22. package/dist/src/ua_variable_impl_ext_obj.d.ts +2 -2
  23. package/dist/src/ua_variable_impl_ext_obj.js +68 -23
  24. package/dist/src/ua_variable_impl_ext_obj.js.map +1 -1
  25. package/package.json +28 -28
  26. package/source/helpers/argument_list.ts +47 -13
  27. package/source/index.ts +6 -3
  28. package/source/loader/load_nodeset2.ts +222 -150
  29. package/source/loader/make_xml_extension_object_parser.ts +80 -33
  30. package/src/data_access/add_dataItem_stuff.ts +2 -2
  31. package/src/data_access/ua_multistate_value_discrete.ts +1 -0
  32. package/src/namespace_impl.ts +1 -29
  33. package/src/ua_variable_impl.ts +93 -18
  34. package/src/ua_variable_impl_ext_obj.ts +79 -30
  35. package/test_helpers/test_fixtures/dataType_with_qualifiedname.xml +71 -0
@@ -1,5 +1,7 @@
1
+ import { Byte, Int16, Int32, Int64, SByte, UAString, UInt16, UInt32 } from "node-opcua-basic-types";
2
+ import { LocalizedTextLike, LocalizedTextOptions } from "node-opcua-data-model";
1
3
  import { make_debugLog, make_warningLog } from "node-opcua-debug";
2
- import { coerceNodeId, NodeId, NodeIdType } from "node-opcua-nodeid";
4
+ import { coerceNodeId, NodeId, NodeIdType, resolveNodeId } from "node-opcua-nodeid";
3
5
  import { EnumDefinition, StructureDefinition } from "node-opcua-types";
4
6
  import { lowerFirstLetter } from "node-opcua-utils";
5
7
  import { DataType } from "node-opcua-variant";
@@ -8,57 +10,110 @@ import { ReaderState, ReaderStateParserLike, ParserLike, XmlAttributes } from "n
8
10
  const warningLog = make_warningLog(__filename);
9
11
  const debugLog = make_debugLog(__filename);
10
12
 
13
+ export interface QualifiedNameOptions {
14
+ namespaceIndex?: UInt16;
15
+ name?: UAString;
16
+ }
17
+ interface QualifiedNameParserChild {
18
+ parent: {
19
+ qualifiedName: QualifiedNameOptions;
20
+ };
21
+ text: string;
22
+ }
23
+ interface QualifiedNameParser {
24
+ value: QualifiedNameOptions;
25
+ qualifiedName: QualifiedNameOptions;
26
+ text: string;
27
+ }
28
+
29
+ const qualifiedNameReader: ReaderStateParserLike = {
30
+ init(this: QualifiedNameParser) {
31
+ this.qualifiedName = {};
32
+ this.value = {};
33
+ },
34
+ parser: {
35
+ Name: {
36
+ finish(this: QualifiedNameParserChild) {
37
+ this.parent.qualifiedName.name = this.text.trim();
38
+ }
39
+ },
40
+ NamespaceIndex: {
41
+ finish(this: QualifiedNameParserChild) {
42
+ const ns = parseInt(this.text, 10);
43
+ this.parent.qualifiedName.namespaceIndex = ns;
44
+ }
45
+ }
46
+ },
47
+ finish(this: QualifiedNameParser) {
48
+ this.value = this.qualifiedName;
49
+ this.value.name = "qdqsdqs";
50
+ }
51
+ };
52
+
53
+ interface LocalizedTextParser {
54
+ localizedText: LocalizedTextOptions;
55
+ value: LocalizedTextOptions;
56
+ }
57
+ interface LocalizedTextChildParser {
58
+ parent: LocalizedTextParser;
59
+ text: string;
60
+ }
11
61
  const localizedTextReader: ReaderStateParserLike = {
12
- init(this: any) {
62
+ init(this: LocalizedTextParser) {
13
63
  this.localizedText = {};
14
64
  },
15
65
  parser: {
16
66
  Locale: {
17
- finish(this: any) {
67
+ finish(this: LocalizedTextChildParser) {
18
68
  this.parent.localizedText = this.parent.localizedText || {};
19
69
  this.parent.localizedText.locale = this.text.trim();
20
70
  }
21
71
  },
22
72
  Text: {
23
- finish(this: any) {
73
+ finish(this: LocalizedTextChildParser) {
24
74
  this.parent.localizedText = this.parent.localizedText || {};
25
75
  this.parent.localizedText.text = this.text.trim();
26
76
  }
27
77
  }
28
78
  },
29
- finish(this: any) {
79
+ finish(this: LocalizedTextParser) {
30
80
  this.value = this.localizedText;
31
81
  }
32
82
  };
33
83
 
34
84
  function clamp(value: number, minValue: number, maxValue: number) {
35
- if(value < minValue) {
85
+ if (value < minValue) {
36
86
  warningLog(`invalid value range : ${value} < ${minValue} but should be [${minValue} , ${maxValue}]`);
37
87
  return minValue;
38
88
  }
39
- if(value > maxValue) {
89
+ if (value > maxValue) {
40
90
  warningLog(`invalid value range : ${value} > ${maxValue} but should be [${minValue} , ${maxValue}]`);
41
91
  return maxValue;
42
92
  }
43
93
  return value;
44
94
  }
45
95
 
96
+ interface Parser<T> {
97
+ value: T | null;
98
+ text: string;
99
+ }
46
100
  const partials: { [key: string]: ReaderStateParserLike } = {
47
101
  LocalizedText: localizedTextReader,
102
+ QualifiedName: qualifiedNameReader,
48
103
  String: {
49
- finish(this: any) {
104
+ finish(this: Parser<string>) {
50
105
  this.value = this.text;
51
106
  }
52
107
  },
53
108
 
54
109
  Boolean: {
55
- finish(this: any) {
110
+ finish(this: Parser<boolean>) {
56
111
  this.value = this.text.toLowerCase() === "true" ? true : false;
57
112
  }
58
113
  },
59
114
 
60
115
  ByteString: {
61
- init(this: any) {
116
+ init(this: Parser<Buffer>) {
62
117
  this.value = null;
63
118
  },
64
119
  finish(this: any) {
@@ -69,74 +124,74 @@ const partials: { [key: string]: ReaderStateParserLike } = {
69
124
  },
70
125
 
71
126
  Float: {
72
- finish(this: any) {
127
+ finish(this: Parser<number>) {
73
128
  this.value = parseFloat(this.text);
74
129
  }
75
130
  },
76
131
 
77
132
  Double: {
78
- finish(this: any) {
133
+ finish(this: Parser<number>) {
79
134
  this.value = parseFloat(this.text);
80
135
  }
81
136
  },
82
137
  Byte: {
83
- finish(this: any) {
138
+ finish(this: Parser<Byte>) {
84
139
  this.value = clamp(parseInt(this.text, 10), 0, 255);
85
140
  }
86
141
  },
87
142
  SByte: {
88
- finish(this: any) {
143
+ finish(this: Parser<SByte>) {
89
144
  this.value = clamp(parseInt(this.text, 10), -128, 127);
90
145
  }
91
146
  },
92
147
  Int8: {
93
- finish(this: any) {
148
+ finish(this: Parser<SByte>) {
94
149
  this.value = clamp(parseInt(this.text, 10), -128, 127);
95
150
  }
96
151
  },
97
152
 
98
153
  Int16: {
99
- finish(this: any) {
154
+ finish(this: Parser<Int16>) {
100
155
  this.value = clamp(parseInt(this.text, 10), -32768, 32767);
101
156
  }
102
157
  },
103
158
  Int32: {
104
- finish(this: any) {
159
+ finish(this: Parser<Int32>) {
105
160
  this.value = clamp(parseInt(this.text, 10), -2147483648, 2147483647);
106
161
  }
107
162
  },
108
163
  Int64: {
109
- finish(this: any) {
164
+ finish(this: Parser<Int32>) {
110
165
  this.value = parseInt(this.text, 10);
111
166
  }
112
167
  },
113
168
 
114
169
  UInt8: {
115
- finish(this: any) {
170
+ finish(this: Parser<Byte>) {
116
171
  this.value = clamp(parseInt(this.text, 10), 0, 255);
117
172
  }
118
173
  },
119
174
 
120
175
  UInt16: {
121
- finish(this: any) {
176
+ finish(this: Parser<UInt16>) {
122
177
  this.value = clamp(parseInt(this.text, 10), 0, 65535);
123
178
  }
124
179
  },
125
180
 
126
181
  UInt32: {
127
- finish(this: any) {
182
+ finish(this: Parser<UInt32>) {
128
183
  this.value = clamp(parseInt(this.text, 10), 0, 4294967295);
129
184
  }
130
185
  },
131
186
 
132
187
  UInt64: {
133
- finish(this: any) {
188
+ finish(this: Parser<UInt32>) {
134
189
  this.value = parseInt(this.text, 10);
135
190
  }
136
191
  },
137
192
 
138
193
  DateTime: {
139
- finish(this: any) {
194
+ finish(this: Parser<Date>) {
140
195
  // to do check Local or GMT
141
196
  this.value = new Date(this.text);
142
197
  }
@@ -150,21 +205,13 @@ const partials: { [key: string]: ReaderStateParserLike } = {
150
205
  },
151
206
 
152
207
  NodeId: {
153
- finish(this: any) {
208
+ finish(this: Parser<NodeId>) {
154
209
  // to do check Local or GMT
155
210
  this.value = coerceNodeId(this.text);
156
211
  }
157
212
  }
158
213
  };
159
214
 
160
- interface Field {
161
- dataType: any;
162
- description?: string;
163
- name: string;
164
- value?: any;
165
- valueRank?: number; // default is -1 => scalar
166
- }
167
-
168
215
  export interface TypeInfo1 {
169
216
  name: string;
170
217
  definition: StructureDefinition;
@@ -247,7 +294,7 @@ function _makeTypeReader(
247
294
  }
248
295
 
249
296
  if (field.valueRank === undefined || field.valueRank === -1) {
250
- // scalar
297
+ // scalar
251
298
  const parser = fieldParser;
252
299
  if (!parser) {
253
300
  throw new Error("??? " + field.dataType + " " + field.name);
@@ -45,7 +45,7 @@ export function add_dataItem_stuff(variable: UAVariable, options: add_dataItem_s
45
45
  const addressSpace = variable.addressSpace;
46
46
  const namespace = addressSpace.getNamespace(variable.nodeId.namespace);
47
47
 
48
- if (Object.prototype.hasOwnProperty.call(options, "definition")) {
48
+ if (Object.prototype.hasOwnProperty.call(options, "definition") && options.definition !== undefined) {
49
49
  namespace.addVariable({
50
50
  browseName: { name: "Definition", namespaceIndex: 0 },
51
51
  dataType: "String",
@@ -58,7 +58,7 @@ export function add_dataItem_stuff(variable: UAVariable, options: add_dataItem_s
58
58
  });
59
59
  }
60
60
 
61
- if (Object.prototype.hasOwnProperty.call(options, "valuePrecision")) {
61
+ if (Object.prototype.hasOwnProperty.call(options, "valuePrecision") && options.valuePrecision !== undefined) {
62
62
  assert(typeof options.valuePrecision === "number");
63
63
 
64
64
  namespace.addVariable({
@@ -264,6 +264,7 @@ export function _addMultiStateValueDiscrete<T, DT extends DataType>(
264
264
  accessLevel: "CurrentRead",
265
265
  browseName: { name: "EnumValues", namespaceIndex: 0 },
266
266
  dataType: "EnumValueType",
267
+ valueRank: 1,
267
268
  minimumSamplingInterval: 0,
268
269
  modellingRule: options.modellingRule ? "Mandatory" : undefined,
269
270
  propertyOf: variable,
@@ -847,21 +847,7 @@ export class NamespaceImpl implements NamespacePrivate {
847
847
  *
848
848
  * });
849
849
  *
850
- * @param options
851
- * @param options.browseName {String}
852
- * @param options.definition {String}
853
- * @param [options.valuePrecision {Double |null} =null]
854
- * @param options.instrumentRange
855
- * @param options.instrumentRange.low {Double}
856
- * @param options.instrumentRange.high {Double}
857
- * @param options.engineeringUnitsRange.low {Double}
858
- * @param options.engineeringUnitsRange.high {Double}
859
- * @param options.engineeringUnits {String}
860
- * @param options.dataType {NodeId} // todo :check
861
- * @param [options.accessLevel {AccessLevelFlag} = "CurrentRead | CurrentWrite"]
862
- * @param [options.userAccessLevel {AccessLevelFlag} = "CurrentRead | CurrentWrite"]
863
- * @param options.value
864
- * @param [options.modellingRule]
850
+
865
851
  * @return {UAVariable}
866
852
  */
867
853
  public addAnalogDataItem<T, DT extends DataType>(options: AddAnalogDataItemOptions): UAAnalogItem<T, DT> {
@@ -881,20 +867,6 @@ export class NamespaceImpl implements NamespacePrivate {
881
867
 
882
868
  const variable = this.addVariable(clone_options) as UAVariableImpl;
883
869
 
884
- // var variable = namespace.addVariable({
885
- // componentOf: options.componentOf,
886
- // organizedBy: options.organizedBy,
887
- // browseName: options.browseName,
888
- // nodeId: options.nodeId,
889
- // value: options.value,
890
- // accessLevel: options.accessLevel,
891
- // userAccessLevel: options.userAccessLevel,
892
- // modellingRule: options.modellingRule
893
- //
894
- // typeDefinition: analogItemType.nodeId,
895
- // dataType: dataType,
896
- // });
897
-
898
870
  add_dataItem_stuff(variable, options);
899
871
 
900
872
  // mandatory (EURange in the specs)
@@ -769,8 +769,14 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
769
769
  dataValue.value = variant1;
770
770
 
771
771
  if (dataValue.value.dataType === DataType.ExtensionObject) {
772
+ const valueIsCorrect = this.checkExtensionObjectIsCorrect(dataValue.value.value);
773
+ if (!valueIsCorrect) {
774
+ errorLog("Invalid value !");
775
+ errorLog(this.toString());
776
+ errorLog(dataValue.toString());
777
+ this.checkExtensionObjectIsCorrect(dataValue.value.value);
778
+ }
772
779
  this.$dataValue = dataValue;
773
- assert(this.checkExtensionObjectIsCorrect(dataValue.value.value));
774
780
  // ----------------------------------
775
781
  if (this.$extensionObject) {
776
782
  // we have an extension object already bound to this node
@@ -1271,7 +1277,18 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1271
1277
  // also bind extension object
1272
1278
  const v = newVariable.$dataValue.value;
1273
1279
  if (v.dataType === DataType.ExtensionObject && v.value && v.arrayType === VariantArrayType.Scalar) {
1274
- newVariable.bindExtensionObject(newVariable.$dataValue.value.value);
1280
+ try {
1281
+ newVariable.bindExtensionObject(newVariable.$dataValue.value.value);
1282
+ } catch (err) {
1283
+ errorLog("Errro binding extension objects");
1284
+ errorLog((err as Error).message);
1285
+ errorLog(this.toString());
1286
+ errorLog("---------------------------------------");
1287
+ errorLog(this.$dataValue.toString());
1288
+ errorLog("---------------------------------------");
1289
+ errorLog(newVariable.$dataValue.toString());
1290
+ throw err;
1291
+ }
1275
1292
  }
1276
1293
  return newVariable;
1277
1294
  }
@@ -1295,22 +1312,68 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1295
1312
  return true;
1296
1313
  }
1297
1314
  const addressSpace = this.addressSpace;
1298
-
1299
- // istanbul ignore next
1300
- if (!(extObj && extObj.constructor)) {
1301
- errorLog(extObj);
1302
- throw new Error("expecting an valid extension object");
1303
- }
1304
1315
  const dataType = addressSpace.findDataType(this.dataType);
1305
1316
  if (!dataType) {
1306
1317
  // may be we are in the process of loading a xml file and the corresponding dataType
1307
1318
  // has not yet been loaded !
1308
1319
  return true;
1309
1320
  }
1310
- try {
1311
- const Constructor = addressSpace.getExtensionObjectConstructor(this.dataType);
1321
+
1322
+ const Constructor = addressSpace.getExtensionObjectConstructor(this.dataType);
1323
+
1324
+ if (this.valueRank === -1) {
1325
+ /** Scalar */
1312
1326
  if (extObj instanceof Array) {
1313
- for (const e of extObj) {
1327
+ return false;
1328
+ }
1329
+ return checkExtensionObjectIsCorrectScalar.call(this, extObj);
1330
+ } else if (this.valueRank === 1) {
1331
+ /** array */
1332
+ if (!(extObj instanceof Array)) {
1333
+ // let's coerce this scalar into an 1-element array if it is a valid extension object
1334
+ if (checkExtensionObjectIsCorrectScalar.call(this, extObj)) {
1335
+ warningLog(
1336
+ `checkExtensionObjectIsCorrect : expecting a array but got a scalar (value rank of ${this.browseName.toString()} is 1)`
1337
+ );
1338
+ extObj = [extObj];
1339
+ } else {
1340
+ return false;
1341
+ }
1342
+ }
1343
+ return checkExtensionObjectIsCorrectArray.call(this, extObj);
1344
+ } else if (this.valueRank === 0) {
1345
+ // Scalar or Array
1346
+ const isCorrectScalar = !Array.isArray(extObj) && checkExtensionObjectIsCorrectScalar.call(this, extObj);
1347
+ const isCorrectArray =
1348
+ Array.isArray(extObj) && checkExtensionObjectIsCorrectArray.call(this, extObj as ExtensionObject[]);
1349
+ return isCorrectArray || isCorrectScalar;
1350
+ } else {
1351
+ throw new Error(
1352
+ `checkExtensionObjectIsCorrect: Not Implemented case, please contact sterfive : this.valueRank =${this.valueRank}`
1353
+ );
1354
+ }
1355
+ function checkExtensionObjectIsCorrectScalar(
1356
+ this: UAVariableImpl,
1357
+ extObj: ExtensionObject | ExtensionObject[] | null
1358
+ ): boolean {
1359
+ // istanbul ignore next
1360
+ if (!(extObj && extObj.constructor)) {
1361
+ errorLog(extObj);
1362
+ throw new Error("expecting an valid extension object");
1363
+ }
1364
+ return extObj.constructor.name === Constructor.name;
1365
+ }
1366
+
1367
+ function checkExtensionObjectIsCorrectArray(this: UAVariableImpl, extObjArray: ExtensionObject[]): boolean {
1368
+ // istanbul ignore next
1369
+ for (const extObj of extObjArray) {
1370
+ if (!(extObj && extObj.constructor)) {
1371
+ errorLog(extObj);
1372
+ throw new Error("expecting an valid extension object");
1373
+ }
1374
+ }
1375
+ try {
1376
+ for (const e of extObjArray) {
1314
1377
  if (!e) {
1315
1378
  continue;
1316
1379
  }
@@ -1320,12 +1383,10 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1320
1383
  }
1321
1384
  }
1322
1385
  return true;
1323
- } else {
1324
- return extObj.constructor.name === Constructor.name;
1386
+ } catch (err) {
1387
+ errorLog(err);
1388
+ return false;
1325
1389
  }
1326
- } catch (err) {
1327
- errorLog(err);
1328
- return false;
1329
1390
  }
1330
1391
  }
1331
1392
 
@@ -1350,10 +1411,24 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1350
1411
  * @method bindExtensionObject
1351
1412
  * @return {ExtensionObject}
1352
1413
  */
1353
- public bindExtensionObject(
1354
- optionalExtensionObject?: ExtensionObject,
1414
+ public bindExtensionObjectScalar(
1415
+ optionalExtensionObject: ExtensionObject,
1355
1416
  options?: BindExtensionObjectOptions
1356
1417
  ): ExtensionObject | null {
1418
+ return this.bindExtensionObject(optionalExtensionObject, options) as ExtensionObject | null;
1419
+ }
1420
+
1421
+ public bindExtensionObjectArray(
1422
+ optionalExtensionObject: ExtensionObject[],
1423
+ options?: BindExtensionObjectOptions
1424
+ ): ExtensionObject[] | null {
1425
+ return this.bindExtensionObject(optionalExtensionObject, options) as ExtensionObject[] | null;
1426
+ }
1427
+
1428
+ public bindExtensionObject(
1429
+ optionalExtensionObject?: ExtensionObject | ExtensionObject[],
1430
+ options?: BindExtensionObjectOptions
1431
+ ): ExtensionObject | ExtensionObject[] | null {
1357
1432
  return _bindExtensionObject(this, optionalExtensionObject, options);
1358
1433
  }
1359
1434
 
@@ -10,7 +10,7 @@ import { NodeId } 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";
13
- import { DataType, Variant, VariantLike } from "node-opcua-variant";
13
+ import { DataType, Variant, VariantLike, VariantArrayType } from "node-opcua-variant";
14
14
 
15
15
  import { valueRankToString } from "./base_node_private";
16
16
  import { UAVariableImpl } from "./ua_variable_impl";
@@ -77,7 +77,6 @@ function makeHandler(variable: UAVariable) {
77
77
  *
78
78
  */
79
79
  export function _touchValue(property: UAVariableImpl, now: PreciseClock): void {
80
-
81
80
  property.$dataValue.sourceTimestamp = now.timestamp;
82
81
  property.$dataValue.sourcePicoseconds = now.picoseconds;
83
82
  property.$dataValue.serverTimestamp = now.timestamp;
@@ -115,13 +114,24 @@ function propagateTouchValueDownward(self: UAVariableImpl, now: PreciseClock): v
115
114
  }
116
115
  }
117
116
 
118
- export function _setExtensionObject(self: UAVariableImpl, ext: ExtensionObject): void {
117
+ export function _setExtensionObject(self: UAVariableImpl, ext: ExtensionObject | ExtensionObject[]): void {
119
118
  // assert(!(ext as any).$isProxy, "internal error ! ExtensionObject has already been proxied !");
120
- ext = unProxy(ext);
121
- self.$extensionObject = new Proxy(ext, makeHandler(self));
122
- self.$dataValue.value.dataType = DataType.ExtensionObject;
123
- self.$dataValue.value.value = self.$extensionObject;
124
- self.$dataValue.statusCode = StatusCodes.Good;
119
+ if (Array.isArray(ext)) {
120
+ assert(self.valueRank === 1, "Only Array is supported for the time being");
121
+ ext = ext.map((e) => unProxy(e));
122
+ self.$dataValue.value.arrayType = VariantArrayType.Array;
123
+ self.$extensionObject = ext.map((e) => new Proxy(e, makeHandler(self)));
124
+ self.$dataValue.value.dataType = DataType.ExtensionObject;
125
+ self.$dataValue.value.value = self.$extensionObject;
126
+ self.$dataValue.statusCode = StatusCodes.Good;
127
+ return;
128
+ } else {
129
+ ext = unProxy(ext);
130
+ self.$extensionObject = new Proxy(ext, makeHandler(self));
131
+ self.$dataValue.value.dataType = DataType.ExtensionObject;
132
+ self.$dataValue.value.value = self.$extensionObject;
133
+ self.$dataValue.statusCode = StatusCodes.Good;
134
+ }
125
135
 
126
136
  const now = getCurrentClock();
127
137
  propagateTouchValueUpward(self, now);
@@ -312,13 +322,15 @@ export function _installExtensionObjectBindingOnProperties(
312
322
  }
313
323
  }
314
324
 
325
+ // eslint-disable-next-line complexity
315
326
  export function _bindExtensionObject(
316
327
  self: UAVariableImpl,
317
- optionalExtensionObject?: ExtensionObject,
328
+ optionalExtensionObject?: ExtensionObject | ExtensionObject[],
318
329
  options?: BindExtensionObjectOptions
319
- ) {
330
+ ): ExtensionObject | ExtensionObject[] | null {
320
331
  options = options || { createMissingProp: false };
321
332
 
333
+
322
334
  const addressSpace = self.addressSpace;
323
335
  const structure = addressSpace.findDataType("Structure");
324
336
  let extensionObject_;
@@ -335,6 +347,11 @@ export function _bindExtensionObject(
335
347
  if (!dt.isSupertypeOf(structure)) {
336
348
  return null;
337
349
  }
350
+
351
+ if (self.valueRank !== -1 && self.valueRank !==1) {
352
+ throw new Error("Cannot bind an extension object here, valueRank must be scalar (-1) or one-dimensional (1)");
353
+ }
354
+
338
355
  // istanbul ignore next
339
356
  if (doDebug) {
340
357
  debugLog(" ------------------------------ binding ", self.browseName.toString(), self.nodeId.toString());
@@ -406,28 +423,60 @@ export function _bindExtensionObject(
406
423
 
407
424
  function innerBindExtensionObject() {
408
425
  if (s.value && (s.value.dataType === DataType.Null || (s.value.dataType === DataType.ExtensionObject && !s.value.value))) {
409
- // create a structure and bind it
410
- extensionObject_ = optionalExtensionObject || addressSpace.constructExtensionObject(self.dataType, {});
411
- // eslint-disable-next-line @typescript-eslint/no-this-alias
412
- self.bindVariable(
413
- {
414
- timestamped_get() {
415
- const d = new DataValue(self.$dataValue);
416
- d.value.value = self.$extensionObject ? self.$extensionObject.clone() : null;
417
- return d;
426
+ if (self.valueRank === -1 /** Scalar */) {
427
+ // create a structure and bind it
428
+ extensionObject_ = optionalExtensionObject || addressSpace.constructExtensionObject(self.dataType, {});
429
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
430
+ self.bindVariable(
431
+ {
432
+ timestamped_get() {
433
+ const d = new DataValue(self.$dataValue);
434
+ d.value.value = self.$extensionObject ? self.$extensionObject.clone() : null;
435
+ return d;
436
+ },
437
+ timestamped_set(dataValue: DataValue, callback: CallbackT<StatusCode>) {
438
+ const ext = dataValue.value.value;
439
+ if (!self.checkExtensionObjectIsCorrect(ext)) {
440
+ return callback(null, StatusCodes.BadInvalidArgument);
441
+ }
442
+ _setExtensionObject(self, ext);
443
+ callback(null, StatusCodes.Good);
444
+ }
418
445
  },
419
- timestamped_set(dataValue: DataValue, callback: CallbackT<StatusCode>) {
420
- const ext = dataValue.value.value;
421
- if (!self.checkExtensionObjectIsCorrect(ext)) {
422
- return callback(null, StatusCodes.BadInvalidArgument);
446
+ true
447
+ );
448
+ _setExtensionObject(self, extensionObject_);
449
+ } else if (self.valueRank === 1 /** Array */) {
450
+ // create a structure and bind it
451
+
452
+ extensionObject_ = optionalExtensionObject || [];
453
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
454
+ self.bindVariable(
455
+ {
456
+ timestamped_get() {
457
+ const d = new DataValue(self.$dataValue);
458
+
459
+ d.value.value = self.$extensionObject
460
+ ? self.$extensionObject.map((e: ExtensionObject) => e.clone())
461
+ : null;
462
+ return d;
463
+ },
464
+ timestamped_set(dataValue: DataValue, callback: CallbackT<StatusCode>) {
465
+ const ext = dataValue.value.value;
466
+ if (!self.checkExtensionObjectIsCorrect(ext)) {
467
+ return callback(null, StatusCodes.BadInvalidArgument);
468
+ }
469
+ _setExtensionObject(self, ext);
470
+ callback(null, StatusCodes.Good);
423
471
  }
424
- _setExtensionObject(self, ext);
425
- callback(null, StatusCodes.Good);
426
- }
427
- },
428
- true
429
- );
430
- _setExtensionObject(self, extensionObject_);
472
+ },
473
+ true
474
+ );
475
+ _setExtensionObject(self, extensionObject_);
476
+ } else {
477
+ errorLog(self.toString());
478
+ errorLog("Unsupported case ! valueRank= ", self.valueRank);
479
+ }
431
480
  } else {
432
481
  // verify that variant has the correct type
433
482
  assert(s.value.dataType === DataType.ExtensionObject);