node-opcua-address-space 2.88.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 (55) hide show
  1. package/dist/source/address_space_ts.d.ts +4 -4
  2. package/dist/source/address_space_ts.js +3 -3
  3. package/dist/source/address_space_ts.js.map +1 -1
  4. package/dist/source/helpers/argument_list.js +8 -12
  5. package/dist/source/helpers/argument_list.js.map +1 -1
  6. package/dist/source/helpers/call_helpers.d.ts +1 -1
  7. package/dist/source/helpers/multiform_func.d.ts +8 -8
  8. package/dist/source/interfaces/alarms_and_conditions/ua_condition_ex.d.ts +1 -1
  9. package/dist/source/interfaces/extension_object_constructor.d.ts +1 -1
  10. package/dist/source/interfaces/state_machine/ua_state_machine_type.d.ts +2 -2
  11. package/dist/source/loader/generateAddressSpaceRaw.d.ts +2 -2
  12. package/dist/source/loader/make_xml_extension_object_parser.d.ts +1 -1
  13. package/dist/source/loader/namespace_post_step.d.ts +1 -1
  14. package/dist/source/session_context.d.ts +1 -1
  15. package/dist/src/address_space.js +11 -11
  16. package/dist/src/address_space.js.map +1 -1
  17. package/dist/src/apply_condition_refresh.d.ts +1 -1
  18. package/dist/src/base_node_impl.js +44 -44
  19. package/dist/src/base_node_impl.js.map +1 -1
  20. package/dist/src/event_data.d.ts +2 -2
  21. package/dist/src/extension_object_array_node.d.ts +1 -1
  22. package/dist/src/extension_object_array_node.js +11 -9
  23. package/dist/src/extension_object_array_node.js.map +1 -1
  24. package/dist/src/nodeid_manager.d.ts +3 -3
  25. package/dist/src/nodeset_tools/nodeset_to_xml.d.ts +1 -1
  26. package/dist/src/reference_impl.js +6 -6
  27. package/dist/src/reference_impl.js.map +1 -1
  28. package/dist/src/tool_isSupertypeOf.d.ts +4 -4
  29. package/dist/src/ua_data_type_impl.js +12 -12
  30. package/dist/src/ua_data_type_impl.js.map +1 -1
  31. package/dist/src/ua_method_impl.js +6 -6
  32. package/dist/src/ua_method_impl.js.map +1 -1
  33. package/dist/src/ua_object_impl.js +7 -7
  34. package/dist/src/ua_object_impl.js.map +1 -1
  35. package/dist/src/ua_object_type_impl.js +6 -6
  36. package/dist/src/ua_object_type_impl.js.map +1 -1
  37. package/dist/src/ua_reference_type_impl.js +6 -6
  38. package/dist/src/ua_reference_type_impl.js.map +1 -1
  39. package/dist/src/ua_variable_impl.d.ts +4 -0
  40. package/dist/src/ua_variable_impl.js +41 -54
  41. package/dist/src/ua_variable_impl.js.map +1 -1
  42. package/dist/src/ua_variable_impl_ext_obj.d.ts +10 -8
  43. package/dist/src/ua_variable_impl_ext_obj.js +287 -240
  44. package/dist/src/ua_variable_impl_ext_obj.js.map +1 -1
  45. package/dist/src/ua_variable_type_impl.js +7 -7
  46. package/dist/src/ua_variable_type_impl.js.map +1 -1
  47. package/dist/src/ua_view_impl.js +3 -3
  48. package/dist/src/ua_view_impl.js.map +1 -1
  49. package/package.json +26 -26
  50. package/source/address_space_ts.ts +1 -1
  51. package/source/helpers/argument_list.ts +26 -26
  52. package/src/extension_object_array_node.ts +11 -10
  53. package/src/ua_variable_impl.ts +40 -51
  54. package/src/ua_variable_impl_ext_obj.ts +333 -288
  55. package/src/ua_variable_type_impl.ts +4 -4
@@ -1,23 +1,20 @@
1
- import * as chalk from "chalk";
2
1
  import assert from "node-opcua-assert";
3
- import { BindExtensionObjectOptions, UADataType, UADynamicVariableArray, UAVariable, UAVariableType } from "node-opcua-address-space-base";
2
+ import { BindExtensionObjectOptions, UADataType, UAVariable, UAVariableType } from "node-opcua-address-space-base";
4
3
  import { coerceQualifiedName, NodeClass } from "node-opcua-data-model";
5
- import { getCurrentClock, PreciseClock, DateWithPicoseconds, coerceClock } from "node-opcua-date-time";
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 { coerceNodeId, NodeId, NodeIdType } 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";
18
- import { bindExtObjArrayNode } from "./extension_object_array_node";
19
17
  import { IndexIterator } from "./idx_iterator";
20
- import { DateTime } from "node-opcua-basic-types";
21
18
 
22
19
  const doDebug = checkDebugFlag(__filename);
23
20
  const debugLog = make_debugLog(__filename);
@@ -33,42 +30,64 @@ function w(str: string, n: number): string {
33
30
  function isProxy(ext: any) {
34
31
  return ext.$isProxy ? true : false;
35
32
  }
33
+ function getProxyVariable(ext: any): UAVariable | null {
34
+ assert(isProxy(ext));
35
+ return ext.$variable as UAVariable | null;
36
+ }
36
37
 
37
- 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 {
38
45
  assert(isProxy(ext));
39
- return ext.$proxyTarget;
46
+ const target = ext.$proxyTarget;
47
+ if (target && isProxy(target)) {
48
+ return getProxyTarget(target);
49
+ }
50
+ return target;
40
51
  }
41
52
 
42
53
  function unProxy(ext: ExtensionObject) {
43
54
  return isProxy(ext) ? getProxyTarget(ext) : ext;
44
55
  }
45
56
 
46
- function _extensionObjectFieldGetter(target: any, key: string /*, receiver*/) {
57
+ function _extensionObjectFieldGetter(getVariable: () => UAVariable | null, target: any, key: string /*, receiver*/) {
47
58
  if (key === "$isProxy") {
48
59
  return true;
49
60
  }
50
61
  if (key === "$proxyTarget") {
51
62
  return target;
52
63
  }
64
+ if (key === "$variable") {
65
+ return getVariable();
66
+ }
67
+
53
68
  if (target[key] === undefined) {
54
69
  return undefined;
55
70
  }
56
71
  return target[key];
57
72
  }
58
-
59
- 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 {
60
74
  target[key] = value;
61
- 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;
62
81
  if (child && child.touchValue) {
63
82
  child.touchValue();
64
83
  }
65
84
  return true; // true means the set operation has succeeded
66
85
  }
67
86
 
68
- function makeHandler(variable: UAVariable) {
87
+ function makeHandler(getVariable: () => UAVariable | null) {
69
88
  const handler = {
70
- get: _extensionObjectFieldGetter,
71
- set: _extensionObjectFieldSetter.bind(null, variable)
89
+ get: _extensionObjectFieldGetter.bind(null, getVariable),
90
+ set: _extensionObjectFieldSetter.bind(null, getVariable)
72
91
  };
73
92
  return handler;
74
93
  }
@@ -85,7 +104,6 @@ export function _touchValue(property: UAVariableImpl, now: PreciseClock): void {
85
104
  property.$dataValue.serverTimestamp = now.timestamp;
86
105
  property.$dataValue.serverPicoseconds = now.picoseconds;
87
106
  property.$dataValue.statusCode = StatusCodes.Good;
88
-
89
107
  if (property.minimumSamplingInterval === 0) {
90
108
  if (property.listenerCount("value_changed") > 0) {
91
109
  const clonedDataValue = property.readValue();
@@ -94,54 +112,48 @@ export function _touchValue(property: UAVariableImpl, now: PreciseClock): void {
94
112
  }
95
113
  }
96
114
 
97
- export function propagateTouchValueUpward(self: UAVariableImpl, now: PreciseClock): void {
115
+ export function propagateTouchValueUpward(self: UAVariableImpl, now: PreciseClock, cache?: Set<UAVariable>): void {
98
116
  _touchValue(self, now);
99
117
  if (self.parent && self.parent.nodeClass === NodeClass.Variable) {
100
118
  const parentVar = self.parent as UAVariableImpl;
101
119
  if (!parentVar.isExtensionObject()) return;
102
- propagateTouchValueUpward(parentVar, now);
120
+
121
+ if (cache) {
122
+ if (cache.has(parentVar)) return;
123
+ cache.add(parentVar);
124
+ }
125
+ propagateTouchValueUpward(parentVar, now, cache);
103
126
  }
104
127
  }
105
128
 
106
- function propagateTouchValueDownward(self: UAVariableImpl, now: PreciseClock): void {
129
+ function propagateTouchValueDownward(self: UAVariableImpl, now: PreciseClock, cache?: Set<UAVariable>): void {
107
130
  if (!self.isExtensionObject()) return;
108
131
  // also propagate changes to embeded variables
109
132
  const dataTypeNode = self.getDataTypeNode();
110
133
  const definition = dataTypeNode.getStructureDefinition();
111
134
  for (const field of definition.fields || []) {
112
135
  const property = self.getChildByName(field.name!) as UAVariableImpl;
136
+
113
137
  if (property) {
138
+
139
+ if (cache) {
140
+ if (cache.has(property)) {
141
+ continue;
142
+ }
143
+ cache.add(property);
144
+ }
145
+
114
146
  _touchValue(property, now);
115
147
  // to do cascade recursivelly ?
116
148
  }
117
149
  }
118
150
  }
119
151
 
120
- export function _setExtensionObject(self: UAVariableImpl, ext: ExtensionObject | ExtensionObject[], sourceTimestamp?: PreciseClock): void {
121
- // assert(!(ext as any).$isProxy, "internal error ! ExtensionObject has already been proxied !");
122
- if (Array.isArray(ext)) {
123
- assert(self.valueRank === 1, "Only Array is supported for the time being");
124
- ext = ext.map((e) => unProxy(e));
125
- self.$dataValue.value.arrayType = VariantArrayType.Array;
126
- self.$extensionObject = ext.map((e) => new Proxy(e, makeHandler(self)));
127
- self.$dataValue.value.dataType = DataType.ExtensionObject;
128
- self.$dataValue.value.value = self.$extensionObject;
129
- self.$dataValue.statusCode = StatusCodes.Good;
130
- return;
131
- } else {
132
- ext = unProxy(ext);
133
- self.$extensionObject = new Proxy(ext, makeHandler(self));
134
- self.$dataValue.value.dataType = DataType.ExtensionObject;
135
- self.$dataValue.value.value = self.$extensionObject;
136
- self.$dataValue.statusCode = StatusCodes.Good;
137
- }
138
152
 
139
- const now = sourceTimestamp || getCurrentClock();
140
- propagateTouchValueUpward(self, now);
141
- propagateTouchValueDownward(self, now);
142
- }
153
+ export function setExtensionObjectPartialValue(node: UAVariableImpl, partialObject: any, sourceTimestamp?: PreciseClock) {
154
+
155
+ const variablesToUpdate: Set<UAVariableImpl> = new Set();
143
156
 
144
- export function setExtensionObjectValue(node: UAVariableImpl, partialObject: any) {
145
157
  const extensionObject = node.$extensionObject;
146
158
  if (!extensionObject) {
147
159
  throw new Error("setExtensionObjectValue node has no extension object " + node.browseName.toString());
@@ -153,12 +165,30 @@ export function setExtensionObjectValue(node: UAVariableImpl, partialObject: any
153
165
  if (extObject[prop] instanceof Object) {
154
166
  _update_extension_object(extObject[prop], partialObject1[prop]);
155
167
  } else {
156
- 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
+ }
157
179
  }
158
180
  }
159
181
  }
160
-
161
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
+ }
162
192
  }
163
193
 
164
194
  function getOrCreateProperty(
@@ -213,155 +243,139 @@ function prepareVariantValue(dataType: DataType, value: VariantLike): VariantLik
213
243
  return value;
214
244
  }
215
245
 
216
- function bindProperty(variableNode: UAVariableImpl, propertyNode: UAVariableImpl, name: string, dataType: DataType) {
217
- // eslint-disable-next-line @typescript-eslint/no-this-alias
218
- propertyNode.bindVariable(
219
- {
220
- timestamped_get: () => {
221
- const propertyValue = variableNode.$extensionObject[name];
222
- if (propertyValue === undefined) {
223
- propertyNode.$dataValue.value.dataType = DataType.Null;
224
- propertyNode.$dataValue.statusCode = StatusCodes.Good;
225
- propertyNode.$dataValue.value.value = null;
226
- 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");
227
269
  }
228
- const value = prepareVariantValue(dataType, propertyValue);
229
- propertyNode.$dataValue.statusCode = StatusCodes.Good;
230
- propertyNode.$dataValue.value.dataType = dataType;
231
- propertyNode.$dataValue.value.value = value;
232
- return new DataValue(propertyNode.$dataValue);
233
- },
234
- timestamped_set: (_dataValue: DataValue, callback: CallbackT<StatusCode>) => {
235
-
236
- propertyNode.setValueFromSource(_dataValue.value, _dataValue.statusCode, _dataValue.sourceTimestamp || new Date());
237
- // callback(null, StatusCodes.BadNotWritable);
238
- callback(null, StatusCodes.Good);
239
270
  }
240
- },
241
- true
242
- );
271
+ }
272
+ }
243
273
  }
244
274
 
275
+ export function _installExtensionObjectBindingOnProperties(
276
+ uaVariable: UAVariableImpl,
277
+ options?: BindExtensionObjectOptions
278
+ ): void {
245
279
 
246
- function _initial_setup(variableNode: UAVariableImpl) {
247
- const dataValue = variableNode.readValue();
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();
248
294
  const extObj = dataValue.value.value;
249
295
  if (extObj instanceof ExtensionObject) {
250
- variableNode.bindExtensionObject(extObj, { createMissingProp: true, force: true });
296
+ uaVariable.bindExtensionObject(extObj, { createMissingProp: true, force: true });
251
297
  } 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 });
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 });
256
302
  } else {
257
303
  throw new Error("Internal Error, unexpected case");
258
304
  }
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
305
  }
265
306
  }
266
- export function _installExtensionObjectBindingOnProperties(
267
- variableNode: UAVariableImpl,
268
- options: BindExtensionObjectOptions
269
- ): void {
270
307
 
271
- if (!variableNode.$extensionObject) {
272
- _initial_setup(variableNode);
273
- return;
274
- }
275
- const addressSpace = variableNode.addressSpace;
276
- const dt = variableNode.getDataTypeNode();
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();
277
315
  const definition = dt.getStructureDefinition();
278
316
 
279
317
  for (const field of definition.fields || []) {
280
- // istanbul ignore next
318
+
281
319
  if (NodeId.sameNodeId(NodeId.nullNodeId, field.dataType)) {
282
320
  warningLog("field.dataType is null ! ", field.name, NodeId.nullNodeId.toString());
283
321
  warningLog(field.toString());
284
322
  warningLog(" dataType replaced with BaseDataType ");
285
323
  warningLog(definition.toString());
286
- field.dataType = variableNode.resolveNodeId("BaseDataType");
324
+ field.dataType = uaVariable.resolveNodeId("BaseDataType");
287
325
  }
288
326
 
289
- const propertyNode = getOrCreateProperty(variableNode, field, options);
327
+ const propertyNode = getOrCreateProperty(uaVariable, field, options);
290
328
  if (!propertyNode) {
291
329
  continue;
292
330
  }
293
- propertyNode.$dataValue.statusCode = StatusCodes.Good;
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();
300
-
301
- const basicDataType = addressSpace.findCorrespondingBasicDataType(field.dataType);
302
331
 
303
- // istanbul ignore next
304
- if (doDebug) {
305
- const x = addressSpace.findNode(field.dataType)!.browseName.toString();
306
- debugLog(
307
- chalk.cyan("xxx"),
308
- " dataType",
309
- w(field.dataType.toString(), 8),
310
- w(field.name!, 25),
311
- "valueRank",
312
- chalk.cyan(w(valueRankToString(field.valueRank), 10)),
313
- chalk.green(w(x, 15)),
314
- "basicType = ",
315
- chalk.yellow(w(basicDataType.toString(), 20)),
316
- propertyNode.nodeId.toString(),
317
- propertyNode.readValue().statusCode.toString()
318
- );
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);
319
349
  }
320
350
 
321
- const camelCaseName = lowerFirstLetter(field.name!);
322
- // assert(Object.prototype.hasOwnProperty.call(variableNode.$extensionObject, camelCaseName));
323
-
324
- if (variableNode.$extensionObject[camelCaseName] !== undefined && basicDataType === DataType.ExtensionObject) {
325
- propertyNode.bindExtensionObject(variableNode.$extensionObject[camelCaseName], { ...options, force: true });
326
- // replace upwards
327
- variableNode.$extensionObject[camelCaseName] = propertyNode.$extensionObject;
328
- } else {
329
- const prop = variableNode.$extensionObject[camelCaseName];
330
- if (prop === undefined) {
331
- propertyNode._internal_set_value(
332
- new Variant({
333
- dataType: DataType.Null
334
- })
335
- );
336
- } else {
337
- const preparedValue = prepareVariantValue(basicDataType, prop);
338
- propertyNode._internal_set_value(
339
- new Variant({
340
- dataType: basicDataType,
341
- value: preparedValue
342
- })
343
- );
344
- }
345
-
346
- // eslint-disable-next-line @typescript-eslint/no-this-alias
347
-
348
- // property.camelCaseName = camelCaseName;
349
- propertyNode.setValueFromSource = function (this: UAVariableImpl, variant: VariantLike) {
350
- // eslint-disable-next-line @typescript-eslint/no-this-alias
351
- const inner_this = this;
352
- const variant1 = Variant.coerce(variant);
353
- inner_this.verifyVariantCompatibility(variant1);
354
-
355
- // because self.$extensionObject is a Proxy with handlers that
356
- // cascade the chagne we do not need to call touchValue() here
357
- variableNode.$extensionObject[camelCaseName] = variant1.value;
358
- };
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);
359
362
  }
360
- assert(propertyNode.readValue().statusCode.equals(StatusCodes.Good));
361
- bindProperty(variableNode, propertyNode, camelCaseName, basicDataType);
362
363
  }
363
364
  }
364
365
 
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
+ }
378
+
365
379
  function isVariableContainingExtensionObject(uaVariable: UAVariableImpl): boolean {
366
380
  const addressSpace = uaVariable.addressSpace;
367
381
  const structure = addressSpace.findDataType("Structure");
@@ -379,38 +393,78 @@ function isVariableContainingExtensionObject(uaVariable: UAVariableImpl): boolea
379
393
  return false;
380
394
  }
381
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);
382
435
 
383
436
  }
384
- // eslint-disable-next-line complexity
437
+
438
+
385
439
  export function _bindExtensionObject(
386
- self: UAVariableImpl,
387
- optionalExtensionObject?: ExtensionObject | ExtensionObject[],
440
+ uaVariable: UAVariableImpl,
441
+ optionalExtensionObject?: ExtensionObject,
388
442
  options?: BindExtensionObjectOptions
389
- ): ExtensionObject | ExtensionObject[] | null {
443
+ ): ExtensionObject | null {
390
444
  options = options || { createMissingProp: false };
391
445
 
392
- if (!isVariableContainingExtensionObject(self)) {
446
+ if (!isVariableContainingExtensionObject(uaVariable)) {
393
447
  return null;
394
448
  }
395
- const addressSpace = self.addressSpace;
449
+
450
+ const addressSpace = uaVariable.addressSpace;
396
451
  let extensionObject_;
397
452
 
398
- if (self.valueRank !== -1 && self.valueRank !== 1) {
453
+ // istanbul ignore next
454
+ if (uaVariable.valueRank !== -1 && uaVariable.valueRank !== 1) {
399
455
  throw new Error("Cannot bind an extension object here, valueRank must be scalar (-1) or one-dimensional (1)");
400
456
  }
401
457
 
402
458
  // istanbul ignore next
403
- if (doDebug) {
404
- debugLog(" ------------------------------ binding ", self.browseName.toString(), self.nodeId.toString());
405
- }
459
+ doDebug && debugLog(" ------------------------------ binding ", uaVariable.browseName.toString(), uaVariable.nodeId.toString());
406
460
 
407
461
  // ignore bindExtensionObject on sub extension object, bindExtensionObject has to be called from the top most object
408
462
  if (
409
463
  !options.force &&
410
- self.parent &&
411
- (self.parent.nodeClass === NodeClass.Variable || self.parent.nodeClass === NodeClass.VariableType)
464
+ uaVariable.parent &&
465
+ (uaVariable.parent.nodeClass === NodeClass.Variable || uaVariable.parent.nodeClass === NodeClass.VariableType)
412
466
  ) {
413
- const parentDataType = (self.parent as UAVariable | UAVariableType).dataType;
467
+ const parentDataType = (uaVariable.parent as UAVariable | UAVariableType).dataType;
414
468
  const dataTypeNode = addressSpace.findNode(parentDataType) as UADataType;
415
469
  const structure = addressSpace.findDataType("Structure")!;
416
470
  // istanbul ignore next
@@ -427,121 +481,73 @@ export function _bindExtensionObject(
427
481
  }
428
482
 
429
483
  // -------------------- make sure we do not bind a variable twice ....
430
- if (self.$extensionObject && !optionalExtensionObject) {
484
+ if (uaVariable.$extensionObject && !optionalExtensionObject) {
431
485
  // istanbul ignore next
432
- if (!self.checkExtensionObjectIsCorrect(self.$extensionObject!)) {
486
+ if (!uaVariable.checkExtensionObjectIsCorrect(uaVariable.$extensionObject!)) {
433
487
  warningLog(
434
488
  "on node : ",
435
- self.browseName.toString(),
436
- self.nodeId.toString(),
489
+ uaVariable.browseName.toString(),
490
+ uaVariable.nodeId.toString(),
437
491
  "dataType=",
438
- self.dataType.toString({ addressSpace: self.addressSpace })
492
+ uaVariable.dataType.toString({ addressSpace: uaVariable.addressSpace })
439
493
  );
440
- warningLog(self.$extensionObject?.toString());
494
+ warningLog(uaVariable.$extensionObject?.toString());
441
495
  throw new Error(
442
496
  "bindExtensionObject: $extensionObject is incorrect: we are expecting a " +
443
- self.dataType.toString({ addressSpace: self.addressSpace }) +
497
+ uaVariable.dataType.toString({ addressSpace: uaVariable.addressSpace }) +
444
498
  " but we got a " +
445
- self.$extensionObject?.constructor.name
499
+ uaVariable.$extensionObject?.constructor.name
446
500
  );
447
501
  }
448
- return self.$extensionObject;
449
- // throw new Error("Variable already bound");
502
+ return uaVariable.$extensionObject;
450
503
  }
451
504
 
452
- // ------------------------------------------------------
453
- // make sure we have a structure
454
- // ------------------------------------------------------
455
- const s = self.readValue();
456
-
457
505
  // istanbul ignore next
458
- if (self.dataTypeObj.isAbstract) {
459
- 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());
460
508
  warningLog("You need to provide a extension object yourself ");
461
509
  throw new Error("bindExtensionObject requires a extensionObject as associated dataType is only abstract");
462
510
  }
463
511
 
512
+ const s = uaVariable.readValue();
464
513
  if (s.value && s.value.dataType === DataType.ExtensionObject && s.value.value && optionalExtensionObject) {
465
514
  // we want to replace the extension object
466
515
  s.value.value = null;
467
516
  }
468
517
  innerBindExtensionObject();
469
- assert(self.$extensionObject instanceof Object);
470
- return self.$extensionObject;
518
+ assert(uaVariable.$extensionObject instanceof Object);
519
+ return uaVariable.$extensionObject;
471
520
 
472
521
  function innerBindExtensionObject() {
473
- const makePreciseClock = (sourceTimestamp: DateTime, picoseconds?: number): PreciseClock =>
474
- ({ timestamp: sourceTimestamp as DateWithPicoseconds, picoseconds: picoseconds || 0 });
475
-
476
522
 
477
523
  if (s.value && (s.value.dataType === DataType.Null || (s.value.dataType === DataType.ExtensionObject && !s.value.value))) {
478
- if (self.valueRank === -1 /** Scalar */) {
479
- // create a structure and bind it
480
- extensionObject_ = optionalExtensionObject || addressSpace.constructExtensionObject(self.dataType, {});
481
- // eslint-disable-next-line @typescript-eslint/no-this-alias
482
- self.bindVariable(
483
- {
484
- timestamped_get() {
485
- const d = new DataValue(self.$dataValue);
486
- d.value.value = self.$extensionObject ? self.$extensionObject.clone() : null;
487
- return d;
488
- },
489
- timestamped_set(dataValue: DataValue, callback: CallbackT<StatusCode>) {
490
- const ext = dataValue.value.value;
491
- if (!self.checkExtensionObjectIsCorrect(ext)) {
492
- return callback(null, StatusCodes.BadInvalidArgument);
493
- }
494
- const sourceTime = coerceClock(dataValue.sourceTimestamp, dataValue.sourcePicoseconds);
495
- _setExtensionObject(self, ext, sourceTime);
496
- callback(null, StatusCodes.Good);
497
- }
498
- },
499
- true
500
- );
501
- const sourceTime = coerceClock(self.$dataValue.sourceTimestamp, self.$dataValue.sourcePicoseconds);
502
- _setExtensionObject(self, extensionObject_, sourceTime);
524
+ if (uaVariable.valueRank === -1 /** Scalar */) {
525
+ extensionObject_ = optionalExtensionObject || addressSpace.constructExtensionObject(uaVariable.dataType, {});
503
526
 
504
- } else if (self.valueRank === 1 /** Array */) {
505
- // create a structure and bind it
527
+ installExt(uaVariable, extensionObject_);
506
528
 
507
- extensionObject_ = optionalExtensionObject || [];
508
- // eslint-disable-next-line @typescript-eslint/no-this-alias
509
- self.bindVariable(
529
+ _innerBindExtensionObjectScalar(uaVariable,
510
530
  {
511
- timestamped_get() {
512
- const d = new DataValue(self.$dataValue);
513
-
514
- d.value.value = self.$extensionObject
515
- ? self.$extensionObject.map((e: ExtensionObject) => e.clone())
516
- : null;
517
- return d;
518
- },
519
- timestamped_set(dataValue: DataValue, callback: CallbackT<StatusCode>) {
520
- const extensionObjectArray = dataValue.value.value;
521
- if (!self.checkExtensionObjectIsCorrect(extensionObjectArray)) {
522
- return callback(null, StatusCodes.BadInvalidArgument);
523
- }
524
- const sourceTime = coerceClock(dataValue.sourceTimestamp, dataValue.sourcePicoseconds);
525
- _setExtensionObject(self, extensionObjectArray, sourceTime);
526
- callback(null, StatusCodes.Good);
527
- }
528
- },
529
- true
530
- );
531
- const sourceTime = coerceClock(self.$dataValue.sourceTimestamp, self.$dataValue.sourcePicoseconds);
532
- _setExtensionObject(self, extensionObject_, sourceTime);
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");
533
537
  } else {
534
- errorLog(self.toString());
535
- errorLog("Unsupported case ! valueRank= ", self.valueRank);
538
+ errorLog(uaVariable.toString());
539
+ errorLog("Unsupported case ! valueRank= ", uaVariable.valueRank);
536
540
  }
537
541
  } else {
538
542
  // verify that variant has the correct type
539
543
  assert(s.value.dataType === DataType.ExtensionObject);
540
- self.$extensionObject = s.value.value;
541
- assert(self.checkExtensionObjectIsCorrect(self.$extensionObject!));
542
- 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);
543
550
  }
544
- _installExtensionObjectBindingOnProperties(self, options!);
545
551
  }
546
552
  }
547
553
 
@@ -560,24 +566,19 @@ const composeBrowseNameAndNodeId = (uaVariable: UAVariable, indexes: number[]) =
560
566
  return { browseName, nodeId };
561
567
  }
562
568
 
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(
569
+
570
+ // eslint-disable-next-line max-statements
571
+ export function _bindExtensionObjectArrayOrMatrix(
571
572
  uaVariable: UAVariableImpl,
572
573
  optionalExtensionObjectArray?: ExtensionObject[],
573
574
  options?: BindExtensionObjectOptions
574
575
  ): ExtensionObject[] {
575
-
576
+ // istanbul ignore next
576
577
  if (uaVariable.valueRank < 1) {
577
578
  throw new Error("Variable must be a MultiDimensional array");
578
579
  }
579
580
  const arrayDimensions = uaVariable.arrayDimensions || [];
580
-
581
+ // istanbul ignore next
581
582
  if (!isVariableContainingExtensionObject(uaVariable)) {
582
583
  return [];
583
584
  }
@@ -601,16 +602,16 @@ export function _bindExtensionObjectMatrix(
601
602
  }
602
603
  }
603
604
  uaVariable.$$extensionObjectArray = optionalExtensionObjectArray;
604
-
605
- // uaVariable.$$getElementBrowseName = getElementBrowseNameByIndex;
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;
606
609
 
607
610
  uaVariable.bindVariable({
608
- get: () => new Variant({
609
- arrayType: VariantArrayType.Array,
610
- dataType: DataType.ExtensionObject,
611
- value: uaVariable.$$extensionObjectArray
612
- })
611
+ get: () => uaVariable.$dataValue.value
613
612
  }, true);
613
+
614
+
614
615
  const namespace = uaVariable.namespace;
615
616
  const indexIterator = new IndexIterator(arrayDimensions);
616
617
  for (let i = 0; i < totalLength; i++) {
@@ -619,28 +620,73 @@ export function _bindExtensionObjectMatrix(
619
620
 
620
621
  const { browseName, nodeId } = composeBrowseNameAndNodeId(uaVariable, index);
621
622
 
622
- const uaElement = namespace.addVariable({
623
- browseName,
624
- nodeId,
625
- componentOf: uaVariable,
626
- dataType: uaVariable.dataType,
627
- valueRank: -1,
628
- accessLevel: uaVariable.userAccessLevel
629
- });
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
+
630
646
  {
631
647
  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
- })
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
+
639
660
  }
640
661
  }
641
662
  return uaVariable.$$extensionObjectArray;
642
663
  }
643
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
+ }
644
690
  export function extractPartialData(path: string | string[], extensionObject: ExtensionObject) {
645
691
  let name;
646
692
  if (typeof path === "string") {
@@ -669,6 +715,5 @@ export function extractPartialData(path: string | string[], extensionObject: Ext
669
715
  }
670
716
  name = path[path.length - 1];
671
717
  c1[name] = c2[name];
672
- c1[name] += 1;
673
718
  return partialData;
674
719
  }