node-opcua-address-space 2.87.0 → 2.89.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.
Files changed (61) hide show
  1. package/LICENSE +1 -1
  2. package/dist/source/address_space_ts.d.ts +4 -4
  3. package/dist/source/address_space_ts.js +3 -3
  4. package/dist/source/address_space_ts.js.map +1 -1
  5. package/dist/source/helpers/argument_list.js +8 -12
  6. package/dist/source/helpers/argument_list.js.map +1 -1
  7. package/dist/source/helpers/call_helpers.d.ts +1 -1
  8. package/dist/source/helpers/multiform_func.d.ts +8 -8
  9. package/dist/source/interfaces/alarms_and_conditions/ua_condition_ex.d.ts +1 -1
  10. package/dist/source/interfaces/extension_object_constructor.d.ts +1 -1
  11. package/dist/source/interfaces/state_machine/ua_state_machine_type.d.ts +2 -2
  12. package/dist/source/loader/generateAddressSpaceRaw.d.ts +2 -2
  13. package/dist/source/loader/make_xml_extension_object_parser.d.ts +1 -1
  14. package/dist/source/loader/namespace_post_step.d.ts +1 -1
  15. package/dist/source/session_context.d.ts +1 -1
  16. package/dist/src/address_space.js +11 -11
  17. package/dist/src/address_space.js.map +1 -1
  18. package/dist/src/apply_condition_refresh.d.ts +1 -1
  19. package/dist/src/base_node_impl.js +44 -44
  20. package/dist/src/base_node_impl.js.map +1 -1
  21. package/dist/src/event_data.d.ts +2 -2
  22. package/dist/src/extension_object_array_node.d.ts +3 -41
  23. package/dist/src/extension_object_array_node.js +20 -54
  24. package/dist/src/extension_object_array_node.js.map +1 -1
  25. package/dist/src/idx_iterator.d.ts +8 -0
  26. package/dist/src/idx_iterator.js +51 -0
  27. package/dist/src/idx_iterator.js.map +1 -0
  28. package/dist/src/nodeid_manager.d.ts +3 -3
  29. package/dist/src/nodeset_tools/nodeset_to_xml.d.ts +1 -1
  30. package/dist/src/reference_impl.js +6 -6
  31. package/dist/src/reference_impl.js.map +1 -1
  32. package/dist/src/tool_isSupertypeOf.d.ts +4 -4
  33. package/dist/src/ua_data_type_impl.js +12 -12
  34. package/dist/src/ua_data_type_impl.js.map +1 -1
  35. package/dist/src/ua_method_impl.js +6 -6
  36. package/dist/src/ua_method_impl.js.map +1 -1
  37. package/dist/src/ua_object_impl.js +7 -7
  38. package/dist/src/ua_object_impl.js.map +1 -1
  39. package/dist/src/ua_object_type_impl.js +6 -6
  40. package/dist/src/ua_object_type_impl.js.map +1 -1
  41. package/dist/src/ua_reference_type_impl.d.ts +1 -1
  42. package/dist/src/ua_reference_type_impl.js +6 -6
  43. package/dist/src/ua_reference_type_impl.js.map +1 -1
  44. package/dist/src/ua_variable_impl.d.ts +15 -9
  45. package/dist/src/ua_variable_impl.js +57 -50
  46. package/dist/src/ua_variable_impl.js.map +1 -1
  47. package/dist/src/ua_variable_impl_ext_obj.d.ts +10 -6
  48. package/dist/src/ua_variable_impl_ext_obj.js +368 -189
  49. package/dist/src/ua_variable_impl_ext_obj.js.map +1 -1
  50. package/dist/src/ua_variable_type_impl.js +7 -7
  51. package/dist/src/ua_variable_type_impl.js.map +1 -1
  52. package/dist/src/ua_view_impl.js +3 -3
  53. package/dist/src/ua_view_impl.js.map +1 -1
  54. package/package.json +40 -40
  55. package/source/address_space_ts.ts +1 -1
  56. package/source/helpers/argument_list.ts +26 -26
  57. package/src/extension_object_array_node.ts +25 -63
  58. package/src/idx_iterator.ts +52 -0
  59. package/src/ua_variable_impl.ts +65 -55
  60. package/src/ua_variable_impl_ext_obj.ts +429 -231
  61. package/src/ua_variable_type_impl.ts +4 -4
@@ -1,20 +1,20 @@
1
- import * as chalk from "chalk";
2
1
  import assert from "node-opcua-assert";
3
2
  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 { coerceQualifiedName, NodeClass } from "node-opcua-data-model";
4
+ import { getCurrentClock, PreciseClock, coerceClock } from "node-opcua-date-time";
6
5
  import { DataValue } from "node-opcua-data-value";
7
6
  import { make_debugLog, make_warningLog, checkDebugFlag, make_errorLog } from "node-opcua-debug";
8
7
  import { ExtensionObject } from "node-opcua-extension-object";
9
- import { NodeId } from "node-opcua-nodeid";
10
- import { StatusCodes, CallbackT, StatusCode } from "node-opcua-status-code";
8
+ import { NodeId, NodeIdType } from "node-opcua-nodeid";
9
+ import { StatusCodes } from "node-opcua-status-code";
11
10
  import { StructureField } from "node-opcua-types";
12
11
  import { lowerFirstLetter } from "node-opcua-utils";
13
- import { DataType, Variant, VariantLike, VariantArrayType } from "node-opcua-variant";
12
+ import { DataType, VariantLike, VariantArrayType } from "node-opcua-variant";
13
+ import { NumericRange } from "node-opcua-numeric-range";
14
14
 
15
- import { valueRankToString } from "./base_node_private";
16
15
  import { UAVariableImpl } from "./ua_variable_impl";
17
16
  import { UADataTypeImpl } from "./ua_data_type_impl";
17
+ import { IndexIterator } from "./idx_iterator";
18
18
 
19
19
  const doDebug = checkDebugFlag(__filename);
20
20
  const debugLog = make_debugLog(__filename);
@@ -30,42 +30,64 @@ function w(str: string, n: number): string {
30
30
  function isProxy(ext: any) {
31
31
  return ext.$isProxy ? true : false;
32
32
  }
33
+ function getProxyVariable(ext: any): UAVariable | null {
34
+ assert(isProxy(ext));
35
+ return ext.$variable as UAVariable | null;
36
+ }
33
37
 
34
- function getProxyTarget(ext: any) {
38
+ function getProxyVariableForProp(ext: any, prop: string) {
39
+ const uaVariable = getProxyVariable(ext);
40
+ if (!uaVariable) return undefined;
41
+ return (uaVariable as any)[prop] as UAVariableImpl | undefined;
42
+ }
43
+
44
+ export function getProxyTarget(ext: any): any {
35
45
  assert(isProxy(ext));
36
- return ext.$proxyTarget;
46
+ const target = ext.$proxyTarget;
47
+ if (target && isProxy(target)) {
48
+ return getProxyTarget(target);
49
+ }
50
+ return target;
37
51
  }
38
52
 
39
53
  function unProxy(ext: ExtensionObject) {
40
54
  return isProxy(ext) ? getProxyTarget(ext) : ext;
41
55
  }
42
56
 
43
- function _extensionObjectFieldGetter(target: any, key: string /*, receiver*/) {
57
+ function _extensionObjectFieldGetter(getVariable: () => UAVariable | null, target: any, key: string /*, receiver*/) {
44
58
  if (key === "$isProxy") {
45
59
  return true;
46
60
  }
47
61
  if (key === "$proxyTarget") {
48
62
  return target;
49
63
  }
64
+ if (key === "$variable") {
65
+ return getVariable();
66
+ }
67
+
50
68
  if (target[key] === undefined) {
51
69
  return undefined;
52
70
  }
53
71
  return target[key];
54
72
  }
55
-
56
- function _extensionObjectFieldSetter(variable: UAVariable, target: any, key: string, value: any /*, receiver*/): boolean {
73
+ function _extensionObjectFieldSetter(getVariable: () => UAVariable | null, target: any, key: string, value: any /*, receiver*/): boolean {
57
74
  target[key] = value;
58
- const child = (variable as any)[key] as UAVariable | null;
75
+ if (isProxy(target)) {
76
+ return true;
77
+ }
78
+ const uaVariable = getVariable();
79
+ if (!uaVariable) return true;
80
+ const child = (uaVariable as any)[key] as UAVariable | null;
59
81
  if (child && child.touchValue) {
60
82
  child.touchValue();
61
83
  }
62
84
  return true; // true means the set operation has succeeded
63
85
  }
64
86
 
65
- function makeHandler(variable: UAVariable) {
87
+ function makeHandler(getVariable: () => UAVariable | null) {
66
88
  const handler = {
67
- get: _extensionObjectFieldGetter,
68
- set: _extensionObjectFieldSetter.bind(null, variable)
89
+ get: _extensionObjectFieldGetter.bind(null, getVariable),
90
+ set: _extensionObjectFieldSetter.bind(null, getVariable)
69
91
  };
70
92
  return handler;
71
93
  }
@@ -82,7 +104,6 @@ export function _touchValue(property: UAVariableImpl, now: PreciseClock): void {
82
104
  property.$dataValue.serverTimestamp = now.timestamp;
83
105
  property.$dataValue.serverPicoseconds = now.picoseconds;
84
106
  property.$dataValue.statusCode = StatusCodes.Good;
85
-
86
107
  if (property.minimumSamplingInterval === 0) {
87
108
  if (property.listenerCount("value_changed") > 0) {
88
109
  const clonedDataValue = property.readValue();
@@ -91,54 +112,48 @@ export function _touchValue(property: UAVariableImpl, now: PreciseClock): void {
91
112
  }
92
113
  }
93
114
 
94
- export function propagateTouchValueUpward(self: UAVariableImpl, now: PreciseClock): void {
115
+ export function propagateTouchValueUpward(self: UAVariableImpl, now: PreciseClock, cache?: Set<UAVariable>): void {
95
116
  _touchValue(self, now);
96
117
  if (self.parent && self.parent.nodeClass === NodeClass.Variable) {
97
118
  const parentVar = self.parent as UAVariableImpl;
98
119
  if (!parentVar.isExtensionObject()) return;
99
- propagateTouchValueUpward(parentVar, now);
120
+
121
+ if (cache) {
122
+ if (cache.has(parentVar)) return;
123
+ cache.add(parentVar);
124
+ }
125
+ propagateTouchValueUpward(parentVar, now, cache);
100
126
  }
101
127
  }
102
128
 
103
- function propagateTouchValueDownward(self: UAVariableImpl, now: PreciseClock): void {
129
+ function propagateTouchValueDownward(self: UAVariableImpl, now: PreciseClock, cache?: Set<UAVariable>): void {
104
130
  if (!self.isExtensionObject()) return;
105
131
  // also propagate changes to embeded variables
106
132
  const dataTypeNode = self.getDataTypeNode();
107
133
  const definition = dataTypeNode.getStructureDefinition();
108
134
  for (const field of definition.fields || []) {
109
135
  const property = self.getChildByName(field.name!) as UAVariableImpl;
136
+
110
137
  if (property) {
138
+
139
+ if (cache) {
140
+ if (cache.has(property)) {
141
+ continue;
142
+ }
143
+ cache.add(property);
144
+ }
145
+
111
146
  _touchValue(property, now);
112
147
  // to do cascade recursivelly ?
113
148
  }
114
149
  }
115
150
  }
116
151
 
117
- export function _setExtensionObject(self: UAVariableImpl, ext: ExtensionObject | ExtensionObject[]): void {
118
- // assert(!(ext as any).$isProxy, "internal error ! ExtensionObject has already been proxied !");
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
- }
135
152
 
136
- const now = getCurrentClock();
137
- propagateTouchValueUpward(self, now);
138
- propagateTouchValueDownward(self, now);
139
- }
153
+ export function setExtensionObjectPartialValue(node: UAVariableImpl, partialObject: any, sourceTimestamp?: PreciseClock) {
154
+
155
+ const variablesToUpdate: Set<UAVariableImpl> = new Set();
140
156
 
141
- export function setExtensionObjectValue(node: UAVariableImpl, partialObject: any) {
142
157
  const extensionObject = node.$extensionObject;
143
158
  if (!extensionObject) {
144
159
  throw new Error("setExtensionObjectValue node has no extension object " + node.browseName.toString());
@@ -150,12 +165,30 @@ export function setExtensionObjectValue(node: UAVariableImpl, partialObject: any
150
165
  if (extObject[prop] instanceof Object) {
151
166
  _update_extension_object(extObject[prop], partialObject1[prop]);
152
167
  } else {
153
- extObject[prop] = partialObject1[prop];
168
+
169
+ if (isProxy(extObject)) {
170
+ // collect element we have to update
171
+ const target = getProxyTarget(extObject);
172
+ assert(!isProxy(target), "something wierd!");
173
+ target[prop] = partialObject1[prop];
174
+ const variable = getProxyVariableForProp(extObject, prop);
175
+ variable && variablesToUpdate.add(variable as UAVariableImpl);
176
+ } else {
177
+ extObject[prop] = partialObject1[prop];
178
+ }
154
179
  }
155
180
  }
156
181
  }
157
-
158
182
  _update_extension_object(extensionObject, partialObject);
183
+
184
+ const now = sourceTimestamp || getCurrentClock();
185
+ const cache: Set<UAVariable> = new Set();
186
+ for (const c of variablesToUpdate) {
187
+ if (cache.has(c)) continue;
188
+ propagateTouchValueUpward(c, now, cache);
189
+ propagateTouchValueDownward(c, now, cache);
190
+ cache.add(c);
191
+ }
159
192
  }
160
193
 
161
194
  function getOrCreateProperty(
@@ -193,7 +226,10 @@ function getOrCreateProperty(
193
226
  browseName: { namespaceIndex: structureNamespace, name: field.name!.toString() },
194
227
  componentOf: variableNode,
195
228
  dataType: field.dataType,
196
- minimumSamplingInterval: variableNode.minimumSamplingInterval
229
+ minimumSamplingInterval: variableNode.minimumSamplingInterval,
230
+ accessLevel: variableNode.accessLevel,
231
+ accessRestrictions: variableNode.accessRestrictions,
232
+ rolePermissions: variableNode.rolePermissions
197
233
  }) as UAVariableImpl;
198
234
  assert(property.minimumSamplingInterval === variableNode.minimumSamplingInterval);
199
235
  }
@@ -207,164 +243,230 @@ function prepareVariantValue(dataType: DataType, value: VariantLike): VariantLik
207
243
  return value;
208
244
  }
209
245
 
210
- function bindProperty(variableNode: UAVariableImpl, propertyNode: UAVariableImpl, name: string, dataType: DataType) {
211
- // eslint-disable-next-line @typescript-eslint/no-this-alias
212
- propertyNode.bindVariable(
213
- {
214
- timestamped_get: () => {
215
- const propertyValue = variableNode.$extensionObject[name];
216
- if (propertyValue === undefined) {
217
- propertyNode.$dataValue.value.dataType = DataType.Null;
218
- propertyNode.$dataValue.statusCode = StatusCodes.Good;
219
- propertyNode.$dataValue.value.value = null;
220
- return new DataValue(propertyNode.$dataValue);
246
+ function installExt(uaVariable: UAVariableImpl, ext: ExtensionObject) {
247
+ ext = unProxy(ext);
248
+ uaVariable.$extensionObject = new Proxy(ext, makeHandler(() => uaVariable));
249
+
250
+
251
+ const addressSpace = uaVariable.addressSpace;
252
+ const definition = uaVariable.dataTypeObj.getStructureDefinition();
253
+ const structure = addressSpace.findDataType("Structure")!;
254
+ for (const field of definition.fields || []) {
255
+ if (field.dataType) {
256
+ const dataTypeNode = addressSpace.findDataType(field.dataType);
257
+ // istanbul ignore next
258
+ if (dataTypeNode && dataTypeNode.isSupertypeOf(structure)) {
259
+ // sub structure .. let make an handler too
260
+ const camelCaseName = lowerFirstLetter(field.name!);
261
+
262
+ const subExtObj = uaVariable.$extensionObject[camelCaseName];
263
+ if (subExtObj) {
264
+ uaVariable.$extensionObject[camelCaseName] = new Proxy(subExtObj, makeHandler(() => {
265
+ return uaVariable.getComponentByName(field.name!) as UAVariable | null;
266
+ }));
267
+ } else {
268
+ warningLog("extension object is null");
221
269
  }
222
- const value = prepareVariantValue(dataType, propertyValue);
223
- propertyNode.$dataValue.statusCode = StatusCodes.Good;
224
- propertyNode.$dataValue.value.dataType = dataType;
225
- propertyNode.$dataValue.value.value = value;
226
- return new DataValue(propertyNode.$dataValue);
227
- },
228
- timestamped_set: (_dataValue: DataValue, callback: CallbackT<StatusCode>) => {
229
- callback(null, StatusCodes.BadNotWritable);
230
270
  }
231
- },
232
- true
233
- );
271
+ }
272
+ }
234
273
  }
235
274
 
236
275
  export function _installExtensionObjectBindingOnProperties(
237
- variableNode: UAVariableImpl,
238
- options: BindExtensionObjectOptions
276
+ uaVariable: UAVariableImpl,
277
+ options?: BindExtensionObjectOptions
239
278
  ): void {
240
- const addressSpace = variableNode.addressSpace;
241
- const dt = variableNode.getDataTypeNode();
279
+
280
+ // may be extension object mechanism has alreday been install
281
+ // in this case we just need to rebind the properties...
282
+ if (uaVariable.$extensionObject) {
283
+ const extObj = uaVariable.$extensionObject;
284
+ uaVariable.bindExtensionObject(extObj, { createMissingProp: true, force: true });
285
+ return;
286
+ }
287
+ if (uaVariable.$$extensionObjectArray) {
288
+ const extObj = uaVariable.$$extensionObjectArray;
289
+ _bindExtensionObjectArrayOrMatrix(uaVariable, extObj, { createMissingProp: true, force: true });
290
+ return;
291
+ }
292
+
293
+ const dataValue = uaVariable.readValue();
294
+ const extObj = dataValue.value.value;
295
+ if (extObj instanceof ExtensionObject) {
296
+ uaVariable.bindExtensionObject(extObj, { createMissingProp: true, force: true });
297
+ } else if (extObj instanceof Array) {
298
+
299
+ // istanbul ignore else
300
+ if (dataValue.value.arrayType === VariantArrayType.Array || dataValue.value.arrayType === VariantArrayType.Matrix) {
301
+ _bindExtensionObjectArrayOrMatrix(uaVariable, extObj, { createMissingProp: true, force: true });
302
+ } else {
303
+ throw new Error("Internal Error, unexpected case");
304
+ }
305
+ }
306
+ }
307
+
308
+ function _installFields2(uaVariable: UAVariableImpl, { get, set }: {
309
+ get: (fieldName: string) => any,
310
+ set: (fieldName: string, value: any, sourceTime: PreciseClock) => void;
311
+ }, options?: BindExtensionObjectOptions) {
312
+
313
+ options = options || { createMissingProp: false };
314
+ const dt = uaVariable.getDataTypeNode();
242
315
  const definition = dt.getStructureDefinition();
243
316
 
244
317
  for (const field of definition.fields || []) {
245
- // istanbul ignore next
318
+
246
319
  if (NodeId.sameNodeId(NodeId.nullNodeId, field.dataType)) {
247
320
  warningLog("field.dataType is null ! ", field.name, NodeId.nullNodeId.toString());
248
321
  warningLog(field.toString());
249
322
  warningLog(" dataType replaced with BaseDataType ");
250
323
  warningLog(definition.toString());
251
- field.dataType = variableNode.resolveNodeId("BaseDataType");
324
+ field.dataType = uaVariable.resolveNodeId("BaseDataType");
252
325
  }
253
326
 
254
- const propertyNode = getOrCreateProperty(variableNode, field, options);
327
+ const propertyNode = getOrCreateProperty(uaVariable, field, options);
255
328
  if (!propertyNode) {
256
329
  continue;
257
330
  }
258
- propertyNode.$dataValue.statusCode = StatusCodes.Good;
259
- propertyNode.touchValue();
260
-
261
- const basicDataType = addressSpace.findCorrespondingBasicDataType(field.dataType);
262
331
 
263
- // istanbul ignore next
264
- if (doDebug) {
265
- const x = addressSpace.findNode(field.dataType)!.browseName.toString();
266
- debugLog(
267
- chalk.cyan("xxx"),
268
- " dataType",
269
- w(field.dataType.toString(), 8),
270
- w(field.name!, 25),
271
- "valueRank",
272
- chalk.cyan(w(valueRankToString(field.valueRank), 10)),
273
- chalk.green(w(x, 15)),
274
- "basicType = ",
275
- chalk.yellow(w(basicDataType.toString(), 20)),
276
- propertyNode.nodeId.toString(),
277
- propertyNode.readValue().statusCode.toString()
278
- );
332
+ propertyNode.$dataValue.statusCode = StatusCodes.Good;
333
+ propertyNode.$dataValue.sourceTimestamp = uaVariable.$dataValue.sourceTimestamp;
334
+ propertyNode.$dataValue.sourcePicoseconds = uaVariable.$dataValue.sourcePicoseconds;
335
+ propertyNode.$dataValue.serverTimestamp = uaVariable.$dataValue.serverTimestamp;
336
+ propertyNode.$dataValue.serverPicoseconds = uaVariable.$dataValue.serverPicoseconds;
337
+ propertyNode.$dataValue.value.dataType = propertyNode.dataTypeObj.basicDataType;
338
+ propertyNode.$dataValue.value.arrayType = propertyNode.valueRank === -1 ? VariantArrayType.Scalar : (propertyNode.valueRank === 1 ? VariantArrayType.Array : VariantArrayType.Matrix)
339
+ propertyNode.$dataValue.value.dimensions =propertyNode.valueRank > 1 ? propertyNode.arrayDimensions : null;
340
+
341
+ const fieldName = field.name!;
342
+ installDataValueGetter(propertyNode, () => get(fieldName));
343
+ assert(propertyNode._inner_replace_dataValue);
344
+ propertyNode._inner_replace_dataValue = (dataValue: DataValue, indexRange?: NumericRange | null) => {
345
+ /** */
346
+ const sourceTime = coerceClock(dataValue.sourceTimestamp, dataValue.sourcePicoseconds);
347
+ const value = dataValue.value.value;
348
+ set(field.name!, value, sourceTime);
279
349
  }
280
350
 
281
- const camelCaseName = lowerFirstLetter(field.name!);
282
- // assert(Object.prototype.hasOwnProperty.call(variableNode.$extensionObject, camelCaseName));
283
-
284
- if (variableNode.$extensionObject[camelCaseName] !== undefined && basicDataType === DataType.ExtensionObject) {
285
- propertyNode.bindExtensionObject(variableNode.$extensionObject[camelCaseName], { ...options, force: true });
286
- // replace upwards
287
- variableNode.$extensionObject[camelCaseName] = propertyNode.$extensionObject;
288
- } else {
289
- const prop = variableNode.$extensionObject[camelCaseName];
290
- if (prop === undefined) {
291
- propertyNode._internal_set_value(
292
- new Variant({
293
- dataType: DataType.Null
294
- })
295
- );
296
- } else {
297
- const preparedValue = prepareVariantValue(basicDataType, prop);
298
- propertyNode._internal_set_value(
299
- new Variant({
300
- dataType: basicDataType,
301
- value: preparedValue
302
- })
303
- );
304
- }
305
-
306
- // eslint-disable-next-line @typescript-eslint/no-this-alias
307
-
308
- // property.camelCaseName = camelCaseName;
309
- propertyNode.setValueFromSource = function (this: UAVariableImpl, variant: VariantLike) {
310
- // eslint-disable-next-line @typescript-eslint/no-this-alias
311
- const inner_this = this;
312
- const variant1 = Variant.coerce(variant);
313
- inner_this.verifyVariantCompatibility(variant1);
314
-
315
- // because self.$extensionObject is a Proxy with handlers that
316
- // cascade the chagne we do not need to call touchValue() here
317
- variableNode.$extensionObject[camelCaseName] = variant1.value;
318
- };
351
+ if (propertyNode.dataTypeObj.basicDataType === DataType.ExtensionObject) {
352
+ _installFields2(propertyNode, {
353
+ get: (fieldName: string) => {
354
+ const mainFieldName = field.name!;
355
+ return get(mainFieldName)[lowerFirstLetter(fieldName)];
356
+ },
357
+ set: (fieldName: string, value: any, sourceTime: PreciseClock) => {
358
+ const mainFieldName = field.name!;
359
+ get(mainFieldName)[lowerFirstLetter(fieldName)] = value;
360
+ }
361
+ }, options);
319
362
  }
320
- assert(propertyNode.readValue().statusCode.equals(StatusCodes.Good));
321
- bindProperty(variableNode, propertyNode, camelCaseName, basicDataType);
322
363
  }
323
364
  }
324
365
 
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
-
366
+ function installDataValueGetter(propertyNode: UAVariableImpl, get: () => any) {
367
+ Object.defineProperty(propertyNode.$dataValue.value, "value", { get });
368
+ const $ = propertyNode.$dataValue;
369
+ Object.defineProperty(propertyNode, "$dataValue", {
370
+ get() {
371
+ return $;
372
+ },
373
+ set: (value) => {
374
+ throw new Error("$dataValue is now frozen and should not be modified this way !\n contact sterfive.com");
375
+ }
376
+ });
377
+ }
333
378
 
334
- const addressSpace = self.addressSpace;
379
+ function isVariableContainingExtensionObject(uaVariable: UAVariableImpl): boolean {
380
+ const addressSpace = uaVariable.addressSpace;
335
381
  const structure = addressSpace.findDataType("Structure");
336
- let extensionObject_;
337
382
 
338
383
  if (!structure) {
339
384
  // the addressSpace is limited and doesn't provide extension object
340
385
  // bindExtensionObject cannot be performed and shall finish here.
341
- return null;
386
+ return false;
342
387
  }
343
388
 
344
389
  assert(structure.browseName.toString() === "Structure", "expecting DataType Structure to be in IAddressSpace");
345
390
 
346
- const dt = self.getDataTypeNode() as UADataTypeImpl;
391
+ const dt = uaVariable.getDataTypeNode() as UADataTypeImpl;
347
392
  if (!dt.isSupertypeOf(structure)) {
393
+ return false;
394
+ }
395
+ return true;
396
+ }
397
+
398
+ function _innerBindExtensionObjectScalar(uaVariable: UAVariableImpl,
399
+ { get, set }: {
400
+ get: () => ExtensionObject;
401
+ set: (value: ExtensionObject, sourceTimestamp: PreciseClock) => void;
402
+ },
403
+ options?: BindExtensionObjectOptions
404
+ ) {
405
+
406
+ uaVariable.$dataValue.statusCode = StatusCodes.Good;
407
+ uaVariable.$dataValue.value.dataType = DataType.ExtensionObject;
408
+ uaVariable.$dataValue.value.arrayType = VariantArrayType.Scalar;
409
+
410
+ uaVariable.setValueFromSource = function (this: UAVariableImpl, variant: VariantLike) {
411
+ setExtensionObjectPartialValue(this, variant.value);
412
+ };
413
+
414
+ installDataValueGetter(uaVariable, get);
415
+ assert(uaVariable._inner_replace_dataValue);
416
+ uaVariable._inner_replace_dataValue = (dataValue: DataValue, indexRange?: NumericRange | null) => {
417
+ /** */
418
+ const ext = dataValue.value.value;
419
+ const sourceTime = coerceClock(dataValue.sourceTimestamp, dataValue.sourcePicoseconds);
420
+ set(ext, sourceTime);
421
+ }
422
+
423
+ _installFields2(uaVariable, {
424
+ get: (fieldName: string) => {
425
+ const extObj = get() as any;
426
+ return extObj[lowerFirstLetter(fieldName)];
427
+ },
428
+ set: (fieldName: string, value: any, sourceTime: PreciseClock) => {
429
+ const extObj = get() as any;
430
+ extObj[lowerFirstLetter(fieldName)] = value;
431
+ //1 propagateTouchValueDownward(uaVariable, sourceTime);
432
+ //1 propagateTouchValueUpward(uaVariable, sourceTime);
433
+ }
434
+ }, options);
435
+
436
+ }
437
+
438
+
439
+ export function _bindExtensionObject(
440
+ uaVariable: UAVariableImpl,
441
+ optionalExtensionObject?: ExtensionObject,
442
+ options?: BindExtensionObjectOptions
443
+ ): ExtensionObject | null {
444
+ options = options || { createMissingProp: false };
445
+
446
+ if (!isVariableContainingExtensionObject(uaVariable)) {
348
447
  return null;
349
448
  }
350
449
 
351
- if (self.valueRank !== -1 && self.valueRank !==1) {
450
+ const addressSpace = uaVariable.addressSpace;
451
+ let extensionObject_;
452
+
453
+ // istanbul ignore next
454
+ if (uaVariable.valueRank !== -1 && uaVariable.valueRank !== 1) {
352
455
  throw new Error("Cannot bind an extension object here, valueRank must be scalar (-1) or one-dimensional (1)");
353
456
  }
354
-
457
+
355
458
  // istanbul ignore next
356
- if (doDebug) {
357
- debugLog(" ------------------------------ binding ", self.browseName.toString(), self.nodeId.toString());
358
- }
459
+ doDebug && debugLog(" ------------------------------ binding ", uaVariable.browseName.toString(), uaVariable.nodeId.toString());
359
460
 
360
461
  // ignore bindExtensionObject on sub extension object, bindExtensionObject has to be called from the top most object
361
462
  if (
362
463
  !options.force &&
363
- self.parent &&
364
- (self.parent.nodeClass === NodeClass.Variable || self.parent.nodeClass === NodeClass.VariableType)
464
+ uaVariable.parent &&
465
+ (uaVariable.parent.nodeClass === NodeClass.Variable || uaVariable.parent.nodeClass === NodeClass.VariableType)
365
466
  ) {
366
- const parentDataType = (self.parent as UAVariable | UAVariableType).dataType;
467
+ const parentDataType = (uaVariable.parent as UAVariable | UAVariableType).dataType;
367
468
  const dataTypeNode = addressSpace.findNode(parentDataType) as UADataType;
469
+ const structure = addressSpace.findDataType("Structure")!;
368
470
  // istanbul ignore next
369
471
  if (dataTypeNode && dataTypeNode.isSupertypeOf(structure)) {
370
472
  // warningLog(
@@ -379,115 +481,212 @@ export function _bindExtensionObject(
379
481
  }
380
482
 
381
483
  // -------------------- make sure we do not bind a variable twice ....
382
- if (self.$extensionObject && !optionalExtensionObject) {
484
+ if (uaVariable.$extensionObject && !optionalExtensionObject) {
383
485
  // istanbul ignore next
384
- if (!self.checkExtensionObjectIsCorrect(self.$extensionObject!)) {
486
+ if (!uaVariable.checkExtensionObjectIsCorrect(uaVariable.$extensionObject!)) {
385
487
  warningLog(
386
488
  "on node : ",
387
- self.browseName.toString(),
388
- self.nodeId.toString(),
489
+ uaVariable.browseName.toString(),
490
+ uaVariable.nodeId.toString(),
389
491
  "dataType=",
390
- self.dataType.toString({ addressSpace: self.addressSpace })
492
+ uaVariable.dataType.toString({ addressSpace: uaVariable.addressSpace })
391
493
  );
392
- warningLog(self.$extensionObject?.toString());
494
+ warningLog(uaVariable.$extensionObject?.toString());
393
495
  throw new Error(
394
496
  "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
497
+ uaVariable.dataType.toString({ addressSpace: uaVariable.addressSpace }) +
498
+ " but we got a " +
499
+ uaVariable.$extensionObject?.constructor.name
398
500
  );
399
501
  }
400
- return self.$extensionObject;
401
- // throw new Error("Variable already bound");
502
+ return uaVariable.$extensionObject;
402
503
  }
403
504
 
404
- // ------------------------------------------------------
405
- // make sure we have a structure
406
- // ------------------------------------------------------
407
- const s = self.readValue();
408
-
409
505
  // istanbul ignore next
410
- if (self.dataTypeObj.isAbstract) {
411
- warningLog("Warning the DataType associated with this Variable is abstract ", self.dataTypeObj.browseName.toString());
506
+ if (uaVariable.dataTypeObj.isAbstract) {
507
+ warningLog("Warning the DataType associated with this Variable is abstract ", uaVariable.dataTypeObj.browseName.toString());
412
508
  warningLog("You need to provide a extension object yourself ");
413
509
  throw new Error("bindExtensionObject requires a extensionObject as associated dataType is only abstract");
414
510
  }
415
511
 
512
+ const s = uaVariable.readValue();
416
513
  if (s.value && s.value.dataType === DataType.ExtensionObject && s.value.value && optionalExtensionObject) {
417
514
  // we want to replace the extension object
418
515
  s.value.value = null;
419
516
  }
420
517
  innerBindExtensionObject();
421
- assert(self.$extensionObject instanceof Object);
422
- return self.$extensionObject;
518
+ assert(uaVariable.$extensionObject instanceof Object);
519
+ return uaVariable.$extensionObject;
423
520
 
424
521
  function innerBindExtensionObject() {
522
+
425
523
  if (s.value && (s.value.dataType === DataType.Null || (s.value.dataType === DataType.ExtensionObject && !s.value.value))) {
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
- }
445
- },
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(
524
+ if (uaVariable.valueRank === -1 /** Scalar */) {
525
+ extensionObject_ = optionalExtensionObject || addressSpace.constructExtensionObject(uaVariable.dataType, {});
526
+
527
+ installExt(uaVariable, extensionObject_);
528
+
529
+ _innerBindExtensionObjectScalar(uaVariable,
455
530
  {
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);
471
- }
472
- },
473
- true
474
- );
475
- _setExtensionObject(self, extensionObject_);
531
+ get: () => uaVariable.$extensionObject,
532
+ set: (value: ExtensionObject) => installExt(uaVariable, value)
533
+ }, options);
534
+ return;
535
+ } else if (uaVariable.valueRank === 1 /** Array */) {
536
+ throw new Error("Should not get there ! Please fix me");
476
537
  } else {
477
- errorLog(self.toString());
478
- errorLog("Unsupported case ! valueRank= ", self.valueRank);
538
+ errorLog(uaVariable.toString());
539
+ errorLog("Unsupported case ! valueRank= ", uaVariable.valueRank);
479
540
  }
480
541
  } else {
481
542
  // verify that variant has the correct type
482
543
  assert(s.value.dataType === DataType.ExtensionObject);
483
- self.$extensionObject = s.value.value;
484
- assert(self.checkExtensionObjectIsCorrect(self.$extensionObject!));
485
- assert(s.statusCode.equals(StatusCodes.Good));
544
+ installExt(uaVariable, s.value.value);
545
+ _innerBindExtensionObjectScalar(uaVariable,
546
+ {
547
+ get: () => uaVariable.$extensionObject,
548
+ set: (value: ExtensionObject) => installExt(uaVariable, value)
549
+ }, options);
550
+ }
551
+ }
552
+ }
553
+
554
+ const getIndexAsText = (index: number | number[]): string => {
555
+ if (typeof index === "number")
556
+ return `${index}`;
557
+ return `${index.map((a) => a.toString()).join(",")}`;
558
+ }
559
+ const composeBrowseNameAndNodeId = (uaVariable: UAVariable, indexes: number[]) => {
560
+ const iAsText = getIndexAsText(indexes);
561
+ const browseName = coerceQualifiedName(iAsText);
562
+ let nodeId: NodeId | undefined;
563
+ if (uaVariable.nodeId.identifierType === NodeIdType.STRING) {
564
+ nodeId = new NodeId(NodeIdType.STRING, uaVariable.nodeId.value as string + `[${iAsText}]`, uaVariable.nodeId.namespace);
565
+ }
566
+ return { browseName, nodeId };
567
+ }
568
+
569
+
570
+ // eslint-disable-next-line max-statements
571
+ export function _bindExtensionObjectArrayOrMatrix(
572
+ uaVariable: UAVariableImpl,
573
+ optionalExtensionObjectArray?: ExtensionObject[],
574
+ options?: BindExtensionObjectOptions
575
+ ): ExtensionObject[] {
576
+ // istanbul ignore next
577
+ if (uaVariable.valueRank < 1) {
578
+ throw new Error("Variable must be a MultiDimensional array");
579
+ }
580
+ const arrayDimensions = uaVariable.arrayDimensions || [];
581
+ // istanbul ignore next
582
+ if (!isVariableContainingExtensionObject(uaVariable)) {
583
+ return [];
584
+ }
585
+ if ((arrayDimensions.length === 0 || arrayDimensions.length === 1 && arrayDimensions[0] === 0) && optionalExtensionObjectArray) {
586
+ arrayDimensions[0] = optionalExtensionObjectArray.length;
587
+ }
588
+
589
+ const totalLength = arrayDimensions.reduce((p, c) => p * c, 1);
590
+
591
+ /** */
592
+ const addressSpace = uaVariable.addressSpace;
593
+ if (optionalExtensionObjectArray) {
594
+ if (optionalExtensionObjectArray.length !== totalLength) {
595
+ throw new Error(`optionalExtensionObjectArray must have the expected number of element matching ${arrayDimensions}`)
596
+ }
597
+ }
598
+ if (!optionalExtensionObjectArray) {
599
+ optionalExtensionObjectArray = [];
600
+ for (let i = 0; i < totalLength; i++) {
601
+ optionalExtensionObjectArray[i] = addressSpace.constructExtensionObject(uaVariable.dataType, {});;
486
602
  }
487
- _installExtensionObjectBindingOnProperties(self, options!);
488
603
  }
604
+ uaVariable.$$extensionObjectArray = optionalExtensionObjectArray;
605
+ uaVariable.$dataValue.value.arrayType = uaVariable.valueRank === 1 ? VariantArrayType.Array : VariantArrayType.Matrix;
606
+ uaVariable.$dataValue.value.dimensions = uaVariable.valueRank === 1 ? null : (uaVariable.arrayDimensions || []);
607
+ uaVariable.$dataValue.value.dataType = DataType.ExtensionObject;
608
+ uaVariable.$dataValue.value.value = uaVariable.$$extensionObjectArray;
609
+
610
+ uaVariable.bindVariable({
611
+ get: () => uaVariable.$dataValue.value
612
+ }, true);
613
+
614
+
615
+ const namespace = uaVariable.namespace;
616
+ const indexIterator = new IndexIterator(arrayDimensions);
617
+ for (let i = 0; i < totalLength; i++) {
618
+
619
+ const index = indexIterator.next();
620
+
621
+ const { browseName, nodeId } = composeBrowseNameAndNodeId(uaVariable, index);
622
+
623
+
624
+
625
+ let uaElement = uaVariable.getComponentByName(browseName) as UAVariableImpl | null;
626
+ if (!uaElement) {
627
+ uaElement = namespace.addVariable({
628
+ browseName,
629
+ nodeId,
630
+ componentOf: uaVariable,
631
+ dataType: uaVariable.dataType,
632
+ valueRank: -1,
633
+ accessLevel: uaVariable.accessLevel
634
+ }) as UAVariableImpl;
635
+ }
636
+
637
+ uaElement.$dataValue.value.dataType = DataType.ExtensionObject;
638
+ uaElement.$dataValue.statusCode = StatusCodes.Good;
639
+ uaElement.$dataValue.sourceTimestamp = uaVariable.$dataValue.sourceTimestamp;
640
+ uaElement.$dataValue.sourcePicoseconds = uaVariable.$dataValue.sourcePicoseconds;
641
+ uaElement.$dataValue.serverTimestamp = uaVariable.$dataValue.serverTimestamp;
642
+ uaElement.$dataValue.serverPicoseconds = uaVariable.$dataValue.serverPicoseconds;
643
+ uaElement.$dataValue.value.dataType = DataType.ExtensionObject;
644
+ uaElement.$dataValue.value.arrayType = VariantArrayType.Scalar;
645
+
646
+ {
647
+ const capturedIndex = i;
648
+ _innerBindExtensionObjectScalar(uaElement,
649
+ {
650
+ get: () => uaVariable.$$extensionObjectArray[capturedIndex],
651
+ set: (newValue: ExtensionObject, sourceTimestamp: PreciseClock) => {
652
+ uaVariable.$$extensionObjectArray[capturedIndex] = newValue;
653
+ uaVariable.touchValue();
654
+ propagateTouchValueDownward(uaVariable, sourceTimestamp);
655
+ propagateTouchValueUpward(uaVariable, sourceTimestamp);
656
+ },
657
+
658
+ }, { ...options, force: true });
659
+
660
+ }
661
+ }
662
+ return uaVariable.$$extensionObjectArray;
489
663
  }
490
664
 
665
+ export function getElement(path: string | string[], data: any) {
666
+ if (typeof path === "string") {
667
+ path = path.split(".");
668
+ }
669
+ let a = data;
670
+ for (const e of path) {
671
+ a = a[e];
672
+ }
673
+ return a;
674
+ }
675
+ export function setElement(path: string | string[], data: any, value: any) {
676
+ if (typeof path === "string") {
677
+ path = path.split(".");
678
+ }
679
+ const last: string = path.pop()!;
680
+ let a = data;
681
+ for (const e of path) {
682
+ a = a[e];
683
+ }
684
+ a[last] = value;
685
+ }
686
+ export function incrementElement(path: string | string[], data: any) {
687
+ const value = getElement(path, data);
688
+ setElement(path, data, value + 1);
689
+ }
491
690
  export function extractPartialData(path: string | string[], extensionObject: ExtensionObject) {
492
691
  let name;
493
692
  if (typeof path === "string") {
@@ -516,6 +715,5 @@ export function extractPartialData(path: string | string[], extensionObject: Ext
516
715
  }
517
716
  name = path[path.length - 1];
518
717
  c1[name] = c2[name];
519
- c1[name] += 1;
520
718
  return partialData;
521
719
  }