node-opcua-address-space 2.59.0 → 2.62.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 (82) hide show
  1. package/dist/source/address_space_ts.d.ts +0 -2
  2. package/dist/source/helpers/argument_list.js +12 -1
  3. package/dist/source/helpers/argument_list.js.map +1 -1
  4. package/dist/source/helpers/multiform_func.d.ts +11 -0
  5. package/dist/source/helpers/multiform_func.js +74 -0
  6. package/dist/source/helpers/multiform_func.js.map +1 -0
  7. package/dist/source/loader/load_nodeset2.js +47 -64
  8. package/dist/source/loader/load_nodeset2.js.map +1 -1
  9. package/dist/source/set_namespace_meta_data.js +1 -1
  10. package/dist/src/address_space.js +12 -6
  11. package/dist/src/address_space.js.map +1 -1
  12. package/dist/src/alarms_and_conditions/condition_snapshot.js +3 -3
  13. package/dist/src/alarms_and_conditions/condition_snapshot.js.map +1 -1
  14. package/dist/src/alarms_and_conditions/ua_alarm_condition_impl.js +1 -1
  15. package/dist/src/alarms_and_conditions/ua_alarm_condition_impl.js.map +1 -1
  16. package/dist/src/alarms_and_conditions/ua_condition_impl.js +8 -6
  17. package/dist/src/alarms_and_conditions/ua_condition_impl.js.map +1 -1
  18. package/dist/src/alarms_and_conditions/ua_off_normal_alarm_impl.js +1 -1
  19. package/dist/src/alarms_and_conditions/ua_off_normal_alarm_impl.js.map +1 -1
  20. package/dist/src/base_node_impl.js +2 -0
  21. package/dist/src/base_node_impl.js.map +1 -1
  22. package/dist/src/base_node_private.d.ts +3 -3
  23. package/dist/src/base_node_private.js +198 -25
  24. package/dist/src/base_node_private.js.map +1 -1
  25. package/dist/src/event_data.js +1 -1
  26. package/dist/src/event_data.js.map +1 -1
  27. package/dist/src/namespace_impl.js +5 -5
  28. package/dist/src/namespace_impl.js.map +1 -1
  29. package/dist/src/nodeset_tools/nodeset_to_xml.js +15 -9
  30. package/dist/src/nodeset_tools/nodeset_to_xml.js.map +1 -1
  31. package/dist/src/nodeset_tools/typedictionary_to_xml.js +17 -10
  32. package/dist/src/nodeset_tools/typedictionary_to_xml.js.map +1 -1
  33. package/dist/src/state_machine/ua_shelving_state_machine_ex.js +20 -13
  34. package/dist/src/state_machine/ua_shelving_state_machine_ex.js.map +1 -1
  35. package/dist/src/ua_data_type_impl.d.ts +15 -5
  36. package/dist/src/ua_data_type_impl.js +129 -51
  37. package/dist/src/ua_data_type_impl.js.map +1 -1
  38. package/dist/src/ua_method_impl.js +2 -1
  39. package/dist/src/ua_method_impl.js.map +1 -1
  40. package/dist/src/ua_object_impl.js +2 -1
  41. package/dist/src/ua_object_impl.js.map +1 -1
  42. package/dist/src/ua_object_type_impl.js +1 -0
  43. package/dist/src/ua_object_type_impl.js.map +1 -1
  44. package/dist/src/ua_variable_impl.d.ts +12 -18
  45. package/dist/src/ua_variable_impl.js +287 -215
  46. package/dist/src/ua_variable_impl.js.map +1 -1
  47. package/dist/src/ua_variable_type_impl.d.ts +3 -4
  48. package/dist/src/ua_variable_type_impl.js +61 -52
  49. package/dist/src/ua_variable_type_impl.js.map +1 -1
  50. package/dist/src/ua_view_impl.js +1 -1
  51. package/dist/src/ua_view_impl.js.map +1 -1
  52. package/distHelpers/mock_session.js +1 -1
  53. package/distHelpers/mock_session.js.map +1 -1
  54. package/package.json +35 -35
  55. package/source/address_space_ts.ts +0 -1
  56. package/source/helpers/argument_list.ts +13 -3
  57. package/source/helpers/multiform_func.ts +76 -0
  58. package/source/loader/load_nodeset2.ts +64 -80
  59. package/source/set_namespace_meta_data.ts +1 -1
  60. package/src/address_space.ts +16 -7
  61. package/src/alarms_and_conditions/condition_snapshot.ts +4 -4
  62. package/src/alarms_and_conditions/ua_alarm_condition_impl.ts +2 -2
  63. package/src/alarms_and_conditions/ua_condition_impl.ts +18 -8
  64. package/src/alarms_and_conditions/ua_off_normal_alarm_impl.ts +1 -1
  65. package/src/base_node_impl.ts +3 -1
  66. package/src/base_node_private.ts +282 -36
  67. package/src/event_data.ts +1 -1
  68. package/src/namespace_impl.ts +6 -6
  69. package/src/nodeset_tools/nodeset_to_xml.ts +20 -10
  70. package/src/nodeset_tools/typedictionary_to_xml.ts +17 -7
  71. package/src/state_machine/ua_shelving_state_machine_ex.ts +28 -16
  72. package/src/ua_data_type_impl.ts +168 -61
  73. package/src/ua_method_impl.ts +10 -2
  74. package/src/ua_object_impl.ts +10 -2
  75. package/src/ua_object_type_impl.ts +1 -0
  76. package/src/ua_variable_impl.ts +421 -325
  77. package/src/ua_variable_type_impl.ts +87 -52
  78. package/src/ua_view_impl.ts +1 -1
  79. package/test_helpers/mock_session.ts +1 -1
  80. package/test_helpers/test_fixtures/fixture_simple_statemachine_nodeset2.xml +18 -0
  81. package/test_helpers/test_fixtures/fixuture_nodeset_objects_with_some_methods.xml +9 -1
  82. package/test_helpers/test_fixtures/mini.Node.Set2.xml +22 -1
@@ -8,7 +8,19 @@
8
8
  // tslint:disable:max-line-length
9
9
  import * as chalk from "chalk";
10
10
 
11
- import { ContinuationData, GetFunc, SetFunc } from "node-opcua-address-space-base";
11
+ import {
12
+ CloneExtraInfo,
13
+ ContinuationData,
14
+ defaultCloneExtraInfo,
15
+ defaultCloneFilter,
16
+ GetFunc,
17
+ SetFunc,
18
+ VariableDataValueGetterCallback,
19
+ VariableDataValueGetterPromise,
20
+ VariableDataValueGetterSync,
21
+ VariableDataValueSetterWithCallback,
22
+ VariableDataValueSetterWithPromise
23
+ } from "node-opcua-address-space-base";
12
24
  import { assert } from "node-opcua-assert";
13
25
  import {
14
26
  isValidDataEncoding,
@@ -37,17 +49,25 @@ import {
37
49
  ReadProcessedDetails,
38
50
  ReadRawModifiedDetails,
39
51
  StructureDefinition,
52
+ StructureField,
40
53
  WriteValueOptions
41
54
  } from "node-opcua-types";
42
55
  import * as utils from "node-opcua-utils";
43
56
  import { lowerFirstLetter } from "node-opcua-utils";
44
- import { Variant, VariantLike, DataType, sameVariant, VariantArrayType, adjustVariant } from "node-opcua-variant";
57
+ import {
58
+ Variant,
59
+ VariantLike,
60
+ DataType,
61
+ sameVariant,
62
+ VariantArrayType,
63
+ adjustVariant,
64
+ verifyRankAndDimensions
65
+ } from "node-opcua-variant";
45
66
  import { StatusCodeCallback } from "node-opcua-status-code";
46
67
  import {
47
68
  IAddressSpace,
48
69
  BindVariableOptions,
49
70
  ContinuationPoint,
50
- DataValueCallback,
51
71
  IVariableHistorian,
52
72
  TimestampGetFunc,
53
73
  TimestampSetFunc,
@@ -56,7 +76,6 @@ import {
56
76
  UAVariableType,
57
77
  CloneOptions,
58
78
  CloneFilter,
59
- CloneExtraInfo,
60
79
  ISessionContext,
61
80
  BaseNode,
62
81
  UAVariableT
@@ -64,6 +83,7 @@ import {
64
83
  import { UAHistoricalDataConfiguration } from "node-opcua-nodeset-ua";
65
84
 
66
85
  import { SessionContext } from "../source/session_context";
86
+ import { convertToCallbackFunction1 } from "../source/helpers/multiform_func";
67
87
  import { BaseNodeImpl, InternalBaseNodeOptions } from "./base_node_impl";
68
88
  import { _clone, ToStringBuilder, UAVariable_toString, valueRankToString } from "./base_node_private";
69
89
  import { EnumerationInfo, IEnumItem, UADataTypeImpl } from "./ua_data_type_impl";
@@ -162,16 +182,16 @@ function validateDataType(
162
182
  if (variantDataType === DataType.Null && !allowNulls) {
163
183
  return false;
164
184
  }
165
- let builtInType: string;
185
+ let builtInType: DataType;
166
186
  let builtInUADataType: UADataType;
167
187
 
168
- const destUADataType = addressSpace.findNode(dataTypeNodeId) as UADataType;
188
+ const destUADataType = addressSpace.findDataType(dataTypeNodeId)!;
169
189
  assert(destUADataType instanceof UADataTypeImpl);
170
190
 
171
191
  if (destUADataType.isAbstract || destUADataType.nodeId.namespace !== 0) {
172
192
  builtInUADataType = destUADataType;
173
193
  } else {
174
- builtInType = DataType[addressSpace.findCorrespondingBasicDataType(destUADataType)];
194
+ builtInType = addressSpace.findCorrespondingBasicDataType(destUADataType);
175
195
  builtInUADataType = addressSpace.findDataType(builtInType)!;
176
196
  }
177
197
  assert(builtInUADataType instanceof UADataTypeImpl);
@@ -183,8 +203,8 @@ function validateDataType(
183
203
  if (destUADataType.isSupertypeOf(enumerationUADataType)) {
184
204
  // istanbul ignore next
185
205
  if (doDebug) {
186
- console.log("destUADataType.", destUADataType.browseName.toString(), destUADataType.nodeId.toString());
187
- console.log(
206
+ debugLog("destUADataType.", destUADataType.browseName.toString(), destUADataType.nodeId.toString());
207
+ debugLog(
188
208
  "enumerationUADataType.",
189
209
  enumerationUADataType.browseName.toString(),
190
210
  enumerationUADataType.nodeId.toString()
@@ -203,19 +223,23 @@ function validateDataType(
203
223
  if (doDebug) {
204
224
  if (dest_isSuperTypeOf_variant) {
205
225
  /* istanbul ignore next*/
206
- console.log(chalk.green(" ---------- Type match !!! "), " on ", nodeId.toString());
226
+ debugLog(chalk.green(" ---------- Type match !!! "), " on ", nodeId.toString());
207
227
  } else {
208
228
  /* istanbul ignore next*/
209
- console.log(chalk.red(" ---------- Type mismatch "), " on ", nodeId.toString());
229
+ debugLog(chalk.red(" ---------- Type mismatch "), " on ", nodeId.toString());
210
230
  }
211
- console.log(chalk.cyan(" Variable data Type is = "), destUADataType.browseName.toString());
212
- console.log(chalk.cyan(" which matches basic Type = "), builtInUADataType.browseName.toString());
213
- console.log(chalk.yellow(" Actual dataType = "), variantUADataType.browseName.toString());
231
+ debugLog(chalk.cyan(" Variable data Type is = "), destUADataType.browseName.toString());
232
+ debugLog(chalk.cyan(" which matches basic Type = "), builtInUADataType.browseName.toString());
233
+ debugLog(chalk.yellow(" Actual dataType = "), variantUADataType.browseName.toString());
214
234
  }
215
235
 
216
236
  return dest_isSuperTypeOf_variant;
217
237
  }
218
238
 
239
+ function default_func(this: UAVariable, dataValue1: DataValue, callback1: CallbackT<StatusCode>) {
240
+ return _default_writable_timestamped_set_func.call(this, dataValue1, callback1);
241
+ }
242
+
219
243
  interface UAVariableOptions extends InternalBaseNodeOptions {
220
244
  value?: any;
221
245
  dataType: NodeId | string;
@@ -227,50 +251,6 @@ interface UAVariableOptions extends InternalBaseNodeOptions {
227
251
  historizing?: number;
228
252
  }
229
253
 
230
- export function verifyRankAndDimensions(options: { valueRank?: number; arrayDimensions?: number[] | null }): void {
231
- // evaluate valueRank arrayDimensions is specified but valueRank is null
232
- if (options.arrayDimensions && options.valueRank === undefined) {
233
- options.valueRank = options.arrayDimensions.length;
234
- }
235
- options.valueRank = options.valueRank === undefined ? -1 : options.valueRank || 0; // UInt32
236
- assert(typeof options.valueRank === "number");
237
-
238
- options.arrayDimensions = options.arrayDimensions || null;
239
- assert(options.arrayDimensions === null || Array.isArray(options.arrayDimensions));
240
-
241
- if (options.arrayDimensions && options.valueRank <= 0) {
242
- throw new Error("[CONFORMANCE] arrayDimensions must be null if valueRank <=0");
243
- }
244
- // specify default arrayDimension if not provided
245
- if (options.valueRank > 0 && !options.arrayDimensions) {
246
- options.arrayDimensions = new Array(options.valueRank).fill(0);
247
- }
248
- if (!options.arrayDimensions && options.valueRank > 0) {
249
- throw new Error("[CONFORMANCE] arrayDimension must be specified if valueRank >0 " + options.valueRank);
250
- }
251
- if (options.valueRank > 0 && options.arrayDimensions!.length !== options.valueRank) {
252
- throw new Error(
253
- "[CONFORMANCE] when valueRank> 0, arrayDimensions must have valueRank elements, this.valueRank =" +
254
- options.valueRank +
255
- " whereas arrayDimensions.length =" +
256
- options.arrayDimensions!.length
257
- );
258
- }
259
- }
260
-
261
- type TimestampGetFunction1 = () => DataValue | Promise<DataValue>;
262
- type TimestampGetFunction2 = (callback: (err: Error | null, dataValue?: DataValue) => void) => void;
263
- type TimestampGetFunction = TimestampGetFunction1 | TimestampGetFunction2;
264
-
265
- type TimestampSetFunction1 = (this: UAVariable, dataValue: DataValue, indexRange: NumericRange) => void | Promise<void>;
266
- type TimestampSetFunction2 = (
267
- this: UAVariable,
268
- dataValue: DataValue,
269
- indexRange: NumericRange,
270
- callback: (err: Error | null, StatusCode: StatusCode) => void
271
- ) => void;
272
- type TimestampSetFunction = TimestampSetFunction1 | TimestampSetFunction2;
273
-
274
254
  /**
275
255
  * A OPCUA Variable Node
276
256
  *
@@ -299,7 +279,9 @@ type TimestampSetFunction = TimestampSetFunction1 | TimestampSetFunction2;
299
279
  */
300
280
  export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
301
281
  public readonly nodeClass = NodeClass.Variable;
282
+
302
283
  public dataType: NodeId;
284
+ private _basicDataType?: DataType;
303
285
 
304
286
  public $extensionObject?: any;
305
287
 
@@ -307,9 +289,9 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
307
289
  public varHistorian?: IVariableHistorian;
308
290
 
309
291
  /**
310
- * @internal
292
+ * @internal @private
311
293
  */
312
- public _dataValue: DataValue;
294
+ public $dataValue: DataValue;
313
295
  public accessLevel: number;
314
296
  public userAccessLevel?: number;
315
297
  public valueRank: number;
@@ -318,11 +300,11 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
318
300
  public semantic_version: number;
319
301
  public arrayDimensions: null | number[];
320
302
 
321
- public _timestamped_get_func?: TimestampGetFunction | null;
322
- public _timestamped_set_func?: TimestampSetFunction | null;
303
+ public _timestamped_get_func?: TimestampGetFunc | null;
304
+ public _timestamped_set_func?: VariableDataValueSetterWithCallback | null;
323
305
  public _get_func: any;
324
306
  public _set_func: any;
325
- public refreshFunc?: (callback: DataValueCallback) => void;
307
+ public refreshFunc?: (callback: CallbackT<DataValue>) => void;
326
308
  public __waiting_callbacks?: any[];
327
309
 
328
310
  get typeDefinitionObj(): UAVariableType {
@@ -346,11 +328,9 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
346
328
 
347
329
  this.minimumSamplingInterval = adjust_samplingInterval(options.minimumSamplingInterval || 0);
348
330
 
349
- this.historizing = !!options.historizing; // coerced to boolean
350
-
351
- this._dataValue = new DataValue({ statusCode: StatusCodes.UncertainInitialValue, value: {} });
331
+ this.historizing = !!options.historizing; // coerced to boolean"
352
332
 
353
- // xx options.value = options.value || { dataType: DataType.Null };
333
+ this.$dataValue = new DataValue({ statusCode: StatusCodes.UncertainInitialValue, value: { dataType: DataType.Null } });
354
334
 
355
335
  if (options.value) {
356
336
  this.bindVariable(options.value);
@@ -453,11 +433,17 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
453
433
 
454
434
  if (this._timestamped_get_func) {
455
435
  if (this._timestamped_get_func.length === 0) {
456
- this._dataValue = (this._timestamped_get_func as TimestampGetFunction1)() as DataValue;
436
+ const dataValueOrPromise = (this._timestamped_get_func as VariableDataValueGetterSync)();
437
+ if (!Object.prototype.hasOwnProperty.call(dataValueOrPromise, "then")) {
438
+ this.$dataValue = dataValueOrPromise as DataValue;
439
+ this.verifyVariantCompatibility(this.$dataValue.value);
440
+ } else {
441
+ errorLog("Unsupported: _timestamped_get_func returns a Promise !");
442
+ }
457
443
  }
458
444
  }
459
445
 
460
- let dataValue = this._dataValue;
446
+ let dataValue = this.$dataValue;
461
447
 
462
448
  if (isGoodish(dataValue.statusCode)) {
463
449
  // note : extractRange will clone the dataValue
@@ -504,12 +490,16 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
504
490
  return dataTypeNode._getEnumerationInfo();
505
491
  }
506
492
 
507
- public asyncRefresh(oldestDate: Date, callback: DataValueCallback): void;
493
+ public asyncRefresh(oldestDate: Date, callback: CallbackT<DataValue>): void;
508
494
  public asyncRefresh(oldestDate: Date): Promise<DataValue>;
509
495
  public asyncRefresh(...args: any[]): any {
496
+ if (isGoodish(this.$dataValue.statusCode)) {
497
+ this.verifyVariantCompatibility(this.$dataValue.value);
498
+ }
499
+
510
500
  const oldestDate = args[0] as Date;
511
501
  assert(oldestDate instanceof Date);
512
- const callback = args[1] as DataValueCallback;
502
+ const callback = args[1] as CallbackT<DataValue>;
513
503
 
514
504
  if (!this.refreshFunc) {
515
505
  // no refresh func
@@ -524,22 +514,36 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
524
514
  }
525
515
  }
526
516
 
527
- if (this._dataValue.serverTimestamp && oldestDate.getTime() <= this._dataValue.serverTimestamp!.getTime()) {
517
+ if (this.$dataValue.serverTimestamp && oldestDate.getTime() <= this.$dataValue.serverTimestamp!.getTime()) {
528
518
  const dataValue = this.readValue();
529
519
  dataValue.serverTimestamp = oldestDate;
530
520
  dataValue.serverPicoseconds = 0;
531
521
  return callback(null, dataValue);
532
522
  }
533
523
 
534
- this.refreshFunc.call(this, (err: Error | null, dataValue?: DataValueLike) => {
535
- if (err || !dataValue) {
536
- dataValue = { statusCode: StatusCodes.BadNoDataAvailable };
537
- }
538
- if (dataValue !== this._dataValue) {
539
- this._internal_set_dataValue(coerceDataValue(dataValue), null);
540
- }
541
- callback(err, this._dataValue);
542
- });
524
+ try {
525
+ this.refreshFunc.call(this, (err: Error | null, dataValue?: DataValueLike) => {
526
+ if (err || !dataValue) {
527
+ errorLog(
528
+ "-------------- refresh call failed",
529
+ this.browseName.toString(),
530
+ this.nodeId.toString(),
531
+ err?.message
532
+ );
533
+ dataValue = { statusCode: StatusCodes.BadNoDataAvailable };
534
+ }
535
+ if (dataValue !== this.$dataValue) {
536
+ this._internal_set_dataValue(coerceDataValue(dataValue), null);
537
+ }
538
+ callback(err, this.$dataValue);
539
+ });
540
+ } catch (err) {
541
+ errorLog("-------------- refresh call failed 2", this.browseName.toString(), this.nodeId.toString());
542
+ errorLog(err);
543
+ const dataValue = new DataValue({ statusCode: StatusCodes.BadInternalError });
544
+ this._internal_set_dataValue(dataValue, null);
545
+ callback(err as Error, this.$dataValue);
546
+ }
543
547
  }
544
548
 
545
549
  public readEnumValue(): IEnumItem {
@@ -629,10 +633,91 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
629
633
  }
630
634
  }
631
635
 
632
- public adjustVariant(variant: Variant): Variant {
636
+ public getBasicDataType(): DataType {
637
+ if (this._basicDataType) {
638
+ return this._basicDataType;
639
+ }
640
+ if (this.dataType.namespace === 0 && this.dataType.value === 0) {
641
+ return DataType.Null;
642
+ }
633
643
  const addressSpace = this.addressSpace;
634
- const dataTypeNodeId = addressSpace.findCorrespondingBasicDataType(this.dataType);
635
- return adjustVariant(variant, this.valueRank, dataTypeNodeId);
644
+ const dataTypeNode = addressSpace.findDataType(this.dataType)!;
645
+ const basicDataType = dataTypeNode ? dataTypeNode.getBasicDataType() : DataType.Null;
646
+ // const basicDataType = addressSpace.findCorrespondingBasicDataType(this.dataType);
647
+ this._basicDataType = basicDataType;
648
+ return basicDataType;
649
+ }
650
+ public adjustVariant(variant: Variant): Variant {
651
+ return adjustVariant(variant, this.valueRank, this.getBasicDataType());
652
+ }
653
+ public verifyVariantCompatibility(variant: Variant): void {
654
+ try {
655
+ // istanbul ignore next
656
+ if (Object.prototype.hasOwnProperty.call(variant, "value")) {
657
+ if (variant.dataType === null || variant.dataType === undefined) {
658
+ throw new Error(
659
+ "Variant must provide a valid dataType : variant = " +
660
+ variant.toString() +
661
+ " this.dataType= " +
662
+ this.dataType.toString()
663
+ );
664
+ }
665
+ if (
666
+ variant.dataType === DataType.Boolean &&
667
+ (this.dataType.namespace !== 0 || this.dataType.value !== DataType.Boolean)
668
+ ) {
669
+ throw new Error(
670
+ "Variant must provide a valid Boolean : variant = " +
671
+ variant.toString() +
672
+ " this.dataType= " +
673
+ this.dataType.toString()
674
+ );
675
+ }
676
+ if (
677
+ this.dataType.namespace === 0 &&
678
+ this.dataType.value === DataType.LocalizedText &&
679
+ variant.dataType !== DataType.LocalizedText
680
+ ) {
681
+ throw new Error(
682
+ "Variant must provide a valid LocalizedText : variant = " +
683
+ variant.toString() +
684
+ " this.dataType= " +
685
+ this.dataType.toString()
686
+ );
687
+ }
688
+ }
689
+ const basicType = this.getBasicDataType();
690
+ if (
691
+ basicType !== DataType.Null &&
692
+ basicType !== DataType.Variant &&
693
+ variant.dataType !== DataType.Null &&
694
+ variant.dataType !== basicType
695
+ ) {
696
+ const message =
697
+ "UAVariable.setValueFromSource " +
698
+ this.browseName.toString() +
699
+ " nodeId:" +
700
+ this.nodeId.toString() +
701
+ " dataType:" +
702
+ this.dataType.toString() +
703
+ ":\n" +
704
+ "the provided variant must have the expected dataType!\n" +
705
+ " - the expected dataType is " +
706
+ chalk.cyan(DataType[basicType]) +
707
+ "\n" +
708
+ " - the actual dataType is " +
709
+ chalk.magenta(DataType[variant.dataType]) +
710
+ "\n" +
711
+ " - " +
712
+ variant.toString();
713
+ throw new Error(message);
714
+ }
715
+ } catch (err) {
716
+ errorLog("UAVariable ", (err as Error)?.message, this.browseName.toString(), " nodeId=", this.nodeId.toString());
717
+ errorLog((err as Error).message);
718
+ errorLog((err as Error).stack);
719
+ throw err;
720
+ }
636
721
  }
637
722
  /**
638
723
  * setValueFromSource is used to let the device sets the variable values
@@ -646,39 +731,26 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
646
731
  * @param [sourceTimestamp= Now]
647
732
  */
648
733
  public setValueFromSource(variant: VariantLike, statusCode?: StatusCode, sourceTimestamp?: Date): void {
649
- statusCode = statusCode || StatusCodes.Good;
650
- // istanbul ignore next
651
- if (Object.prototype.hasOwnProperty.call(variant, "value")) {
652
- if (variant.dataType === null || variant.dataType === undefined) {
653
- throw new Error("Variant must provide a valid dataType" + variant.toString());
654
- }
655
- if (
656
- variant.dataType === DataType.Boolean &&
657
- (this.dataType.namespace !== 0 || this.dataType.value !== DataType.Boolean)
658
- ) {
659
- throw new Error("Variant must provide a valid Boolean" + variant.toString());
660
- }
661
- if (
662
- this.dataType.namespace === 0 &&
663
- this.dataType.value === DataType.LocalizedText &&
664
- variant.dataType !== DataType.LocalizedText
665
- ) {
666
- throw new Error("Variant must provide a valid LocalizedText" + variant.toString());
667
- }
734
+ try {
735
+ statusCode = statusCode || StatusCodes.Good;
736
+ const variant1 = Variant.coerce(variant);
737
+ this.verifyVariantCompatibility(variant1);
738
+ const now = coerceClock(sourceTimestamp, 0);
739
+
740
+ const dataValue = new DataValue(null);
741
+ dataValue.serverPicoseconds = now.picoseconds;
742
+ dataValue.serverTimestamp = now.timestamp;
743
+ dataValue.sourcePicoseconds = now.picoseconds;
744
+ dataValue.sourceTimestamp = now.timestamp;
745
+ dataValue.statusCode = statusCode;
746
+ dataValue.value = variant1;
747
+ this._internal_set_dataValue(dataValue);
748
+ } catch (err) {
749
+ errorLog("UAVariable#setValueFromString Error : ", this.browseName.toString(), this.nodeId.toString());
750
+ errorLog((err as Error).message);
751
+ errorLog(this.parent?.toString());
752
+ throw err;
668
753
  }
669
-
670
- const variant1 = Variant.coerce(variant);
671
-
672
- const now = coerceClock(sourceTimestamp, 0);
673
-
674
- const dataValue = new DataValue(null);
675
- dataValue.serverPicoseconds = now.picoseconds;
676
- dataValue.serverTimestamp = now.timestamp;
677
- dataValue.sourcePicoseconds = now.picoseconds;
678
- dataValue.sourceTimestamp = now.timestamp;
679
- dataValue.statusCode = statusCode;
680
- dataValue.value = variant1;
681
- this._internal_set_dataValue(dataValue);
682
754
  }
683
755
 
684
756
  public writeValue(
@@ -749,16 +821,7 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
749
821
  return callback!(null, statusCode);
750
822
  }
751
823
 
752
- function default_func(
753
- this: UAVariable,
754
- dataValue1: DataValue,
755
- indexRange1: NumericRange,
756
- callback1: (err: Error | null, statusCode: StatusCode, dataValue?: DataValue | null | undefined) => void
757
- ) {
758
- // xx assert(!indexRange,"indexRange Not Implemented");
759
- return _default_writable_timestamped_set_func.call(this, dataValue1, callback1);
760
- }
761
- const write_func = (this._timestamped_set_func || default_func) as any;
824
+ const write_func = this._timestamped_set_func || default_func;
762
825
 
763
826
  if (!write_func) {
764
827
  warningLog(" warning " + this.nodeId.toString() + " " + this.browseName.toString() + " has no setter. \n");
@@ -767,77 +830,70 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
767
830
  }
768
831
  assert(write_func);
769
832
 
770
- write_func.call(
771
- this,
772
- dataValue,
773
- indexRange,
774
- (err: Error | null, statusCode1?: StatusCode, correctedDataValue?: DataValue) => {
775
- if (!err) {
776
- correctedDataValue = correctedDataValue || dataValue;
777
- assert(correctedDataValue instanceof DataValue);
778
- // xx assert(correctedDataValue.serverTimestamp);
779
-
780
- if (indexRange && !indexRange.isEmpty()) {
781
- if (!indexRange.isValid()) {
782
- return callback!(null, StatusCodes.BadIndexRangeInvalid);
833
+ write_func.call(this, dataValue, (err?: Error | null, statusCode1?: StatusCode) => {
834
+ if (!err) {
835
+ dataValue && this.verifyVariantCompatibility(dataValue.value);
836
+
837
+ if (indexRange && !indexRange.isEmpty()) {
838
+ if (!indexRange.isValid()) {
839
+ return callback!(null, StatusCodes.BadIndexRangeInvalid);
840
+ }
841
+
842
+ const newArrayOrMatrix = dataValue.value.value;
843
+
844
+ if (dataValue.value.arrayType === VariantArrayType.Array) {
845
+ if (this.$dataValue.value.arrayType !== VariantArrayType.Array) {
846
+ return callback(null, StatusCodes.BadTypeMismatch);
783
847
  }
848
+ // check that destination data is also an array
849
+ assert(check_valid_array(this.$dataValue.value.dataType, this.$dataValue.value.value));
850
+ const destArr = this.$dataValue.value.value;
851
+ const result = indexRange.set_values(destArr, newArrayOrMatrix);
784
852
 
785
- const newArrayOrMatrix = correctedDataValue.value.value;
786
-
787
- if (correctedDataValue.value.arrayType === VariantArrayType.Array) {
788
- if (this._dataValue.value.arrayType !== VariantArrayType.Array) {
789
- return callback(null, StatusCodes.BadTypeMismatch);
790
- }
791
- // check that destination data is also an array
792
- assert(check_valid_array(this._dataValue.value.dataType, this._dataValue.value.value));
793
- const destArr = this._dataValue.value.value;
794
- const result = indexRange.set_values(destArr, newArrayOrMatrix);
795
-
796
- if (result.statusCode.isNot(StatusCodes.Good)) {
797
- return callback!(null, result.statusCode);
798
- }
799
- correctedDataValue.value.value = result.array;
800
-
801
- // scrap original array so we detect range
802
- this._dataValue.value.value = null;
803
- } else if (correctedDataValue.value.arrayType === VariantArrayType.Matrix) {
804
- const dimensions = this._dataValue.value.dimensions;
805
- if (this._dataValue.value.arrayType !== VariantArrayType.Matrix || !dimensions) {
806
- // not a matrix !
807
- return callback!(null, StatusCodes.BadTypeMismatch);
808
- }
809
- const matrix = this._dataValue.value.value;
810
- const result = indexRange.set_values_matrix(
811
- {
812
- matrix,
813
- dimensions
814
- },
815
- newArrayOrMatrix
816
- );
817
- if (result.statusCode.isNot(StatusCodes.Good)) {
818
- return callback!(null, result.statusCode);
819
- }
820
- correctedDataValue.value.dimensions = this._dataValue.value.dimensions;
821
- correctedDataValue.value.value = result.matrix;
822
-
823
- // scrap original array so we detect range
824
- this._dataValue.value.value = null;
825
- } else {
853
+ if (result.statusCode.isNot(StatusCodes.Good)) {
854
+ return callback!(null, result.statusCode);
855
+ }
856
+ dataValue.value.value = result.array;
857
+
858
+ // scrap original array so we detect range
859
+ this.$dataValue.value.value = null;
860
+ } else if (dataValue.value.arrayType === VariantArrayType.Matrix) {
861
+ const dimensions = this.$dataValue.value.dimensions;
862
+ if (this.$dataValue.value.arrayType !== VariantArrayType.Matrix || !dimensions) {
863
+ // not a matrix !
826
864
  return callback!(null, StatusCodes.BadTypeMismatch);
827
865
  }
828
- }
829
- try {
830
- this._internal_set_dataValue(correctedDataValue, indexRange);
831
- } catch (err) {
832
- if (err instanceof Error) {
833
- warningLog(err.message);
866
+ const matrix = this.$dataValue.value.value;
867
+ const result = indexRange.set_values_matrix(
868
+ {
869
+ matrix,
870
+ dimensions
871
+ },
872
+ newArrayOrMatrix
873
+ );
874
+ if (result.statusCode.isNot(StatusCodes.Good)) {
875
+ return callback!(null, result.statusCode);
834
876
  }
835
- return callback!(null, StatusCodes.BadInternalError);
877
+ dataValue.value.dimensions = this.$dataValue.value.dimensions;
878
+ dataValue.value.value = result.matrix;
879
+
880
+ // scrap original array so we detect range
881
+ this.$dataValue.value.value = null;
882
+ } else {
883
+ return callback!(null, StatusCodes.BadTypeMismatch);
884
+ }
885
+ }
886
+ try {
887
+ this._internal_set_dataValue(dataValue, indexRange);
888
+ } catch (err) {
889
+ if (err instanceof Error) {
890
+ warningLog(err.message);
836
891
  }
892
+ return callback!(null, StatusCodes.BadInternalError);
837
893
  }
838
- callback!(err, statusCode1);
839
894
  }
840
- );
895
+ callback!(err || null, statusCode1);
896
+ });
841
897
  }
842
898
 
843
899
  public writeAttribute(context: ISessionContext | null, writeValue: WriteValueOptions, callback: StatusCodeCallback): void;
@@ -903,6 +959,11 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
903
959
  if (!this._validate_DataType(value.dataType)) {
904
960
  return StatusCodes.BadTypeMismatch;
905
961
  }
962
+ try {
963
+ this.verifyVariantCompatibility(value);
964
+ } catch (err) {
965
+ return StatusCodes.BadTypeMismatch;
966
+ }
906
967
  return StatusCodes.Good;
907
968
  }
908
969
 
@@ -917,12 +978,12 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
917
978
  */
918
979
  public touchValue(optionalNow?: PreciseClock): void {
919
980
  const now = optionalNow || getCurrentClock();
920
- this._dataValue.sourceTimestamp = now.timestamp;
921
- this._dataValue.sourcePicoseconds = now.picoseconds;
922
- this._dataValue.serverTimestamp = now.timestamp;
923
- this._dataValue.serverPicoseconds = now.picoseconds;
981
+ this.$dataValue.sourceTimestamp = now.timestamp;
982
+ this.$dataValue.sourcePicoseconds = now.picoseconds;
983
+ this.$dataValue.serverTimestamp = now.timestamp;
984
+ this.$dataValue.serverPicoseconds = now.picoseconds;
924
985
 
925
- this._dataValue.statusCode = StatusCodes.Good;
986
+ this.$dataValue.statusCode = StatusCodes.Good;
926
987
 
927
988
  if (this.minimumSamplingInterval === 0) {
928
989
  if (this.listenerCount("value_changed") > 0) {
@@ -1070,6 +1131,7 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1070
1131
 
1071
1132
  assert(typeof this._timestamped_set_func !== "function", "UAVariable already bound");
1072
1133
  assert(typeof this._timestamped_get_func !== "function", "UAVariable already bound");
1134
+
1073
1135
  bind_getter.call(this, options);
1074
1136
  bind_setter.call(this, options);
1075
1137
 
@@ -1081,9 +1143,9 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1081
1143
  this._historyRead = _historyRead;
1082
1144
  assert(this._historyRead.length === 6);
1083
1145
  }
1084
-
1146
+ // post conditions
1085
1147
  assert(typeof this._timestamped_set_func === "function");
1086
- assert(this._timestamped_set_func!.length === 3);
1148
+ assert(this._timestamped_set_func!.length === 2, "expecting 2 parameters");
1087
1149
  }
1088
1150
 
1089
1151
  /**
@@ -1106,7 +1168,7 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1106
1168
  }
1107
1169
 
1108
1170
  const readImmediate = (innerCallback: (err: Error | null, dataValue: DataValue) => void) => {
1109
- assert(this._dataValue instanceof DataValue);
1171
+ assert(this.$dataValue instanceof DataValue);
1110
1172
  const dataValue = this.readValue();
1111
1173
  innerCallback(null, dataValue);
1112
1174
  };
@@ -1174,14 +1236,20 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1174
1236
  valueRank: this.valueRank
1175
1237
  };
1176
1238
 
1177
- const newVariable = _clone.call(this, UAVariableImpl, options, optionalFilter, extraInfo) as UAVariableImpl;
1239
+ const newVariable = _clone.call(
1240
+ this,
1241
+ UAVariableImpl,
1242
+ options,
1243
+ optionalFilter || defaultCloneFilter,
1244
+ extraInfo || defaultCloneExtraInfo
1245
+ ) as UAVariableImpl;
1178
1246
 
1179
1247
  newVariable.bindVariable();
1180
1248
 
1181
1249
  assert(typeof newVariable._timestamped_set_func === "function");
1182
1250
 
1183
1251
  assert(newVariable.dataType === this.dataType);
1184
- newVariable._dataValue = this._dataValue.clone();
1252
+ newVariable.$dataValue = this.$dataValue.clone();
1185
1253
  return newVariable;
1186
1254
  }
1187
1255
 
@@ -1204,8 +1272,10 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1204
1272
  return true;
1205
1273
  }
1206
1274
  const addressSpace = this.addressSpace;
1275
+
1276
+ // istanbul ignore next
1207
1277
  if (!(extObj && extObj.constructor)) {
1208
- console.log(extObj);
1278
+ errorLog(extObj);
1209
1279
  throw new Error("expecting an valid extension object");
1210
1280
  }
1211
1281
  const dataType = addressSpace.findDataType(this.dataType);
@@ -1231,7 +1301,7 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1231
1301
  return extObj.constructor.name === Constructor.name;
1232
1302
  }
1233
1303
  } catch (err) {
1234
- console.log(err);
1304
+ errorLog(err);
1235
1305
  return false;
1236
1306
  }
1237
1307
  }
@@ -1252,7 +1322,7 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1252
1322
 
1253
1323
  // istanbul ignore next
1254
1324
  if (doDebug) {
1255
- console.log(" ------------------------------ binding ", this.browseName.toString(), this.nodeId.toString());
1325
+ debugLog(" ------------------------------ binding ", this.browseName.toString(), this.nodeId.toString());
1256
1326
  }
1257
1327
  assert(structure && structure.browseName.toString() === "Structure", "expecting DataType Structure to be in IAddressSpace");
1258
1328
 
@@ -1277,14 +1347,14 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1277
1347
  // }
1278
1348
  // istanbul ignore next
1279
1349
  if (!this.checkExtensionObjectIsCorrect(this.$extensionObject!)) {
1280
- console.log(
1350
+ warningLog(
1281
1351
  "on node : ",
1282
1352
  this.browseName.toString(),
1283
1353
  this.nodeId.toString(),
1284
1354
  "dataType=",
1285
1355
  this.dataType.toString({ addressSpace: this.addressSpace })
1286
1356
  );
1287
- console.log(this.$extensionObject?.toString());
1357
+ warningLog(this.$extensionObject?.toString());
1288
1358
  throw new Error(
1289
1359
  "bindExtensionObject: $extensionObject is incorrect: we are expecting a " +
1290
1360
  this.dataType.toString({ addressSpace: this.addressSpace }) +
@@ -1299,45 +1369,32 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1299
1369
 
1300
1370
  // ------------------------------------------------------------------
1301
1371
 
1302
- function prepareVariantValue(dataType: DataType | string, value: VariantLike): VariantLike {
1303
- if (typeof dataType === "string") {
1304
- dataType = (DataType as any)[dataType];
1305
- }
1372
+ function prepareVariantValue(dataType: DataType, value: VariantLike): VariantLike {
1306
1373
  if ((dataType === DataType.Int32 || dataType === DataType.UInt32) && value && (value as any).key) {
1307
1374
  value = value.value;
1308
1375
  }
1309
1376
  return value;
1310
1377
  }
1311
1378
 
1312
- const bindProperty = (propertyNode: UAVariableImpl, name: any, extensionObject: any, dataTypeNodeId: any) => {
1313
- const dataTypeAsString = DataType[dataTypeNodeId];
1314
-
1315
- /*
1316
- property.setValueFromSource(new Variant({
1317
- dataType: dataType,
1318
- value: prepareVariantValue(dataType, this.$extensionObject[name])
1319
- }));
1320
- */
1321
-
1322
- assert(propertyNode.readValue().statusCode.equals(StatusCodes.Good));
1323
-
1379
+ const bindProperty = (propertyNode: UAVariableImpl, name: string, extensionObject: ExtensionObject, dataType: DataType) => {
1324
1380
  // eslint-disable-next-line @typescript-eslint/no-this-alias
1325
1381
  const self = this;
1326
1382
  propertyNode.bindVariable(
1327
1383
  {
1328
1384
  timestamped_get: () => {
1329
- const prop = self.$extensionObject[name];
1385
+ const propertyValue = self.$extensionObject[name];
1330
1386
 
1331
- if (prop === undefined) {
1332
- propertyNode._dataValue.value.dataType = DataType.Null;
1333
- propertyNode._dataValue.statusCode = StatusCodes.Good;
1334
- propertyNode._dataValue.value.value = null;
1335
- return new DataValue(propertyNode._dataValue);
1387
+ if (propertyValue === undefined) {
1388
+ propertyNode.$dataValue.value.dataType = DataType.Null;
1389
+ propertyNode.$dataValue.statusCode = StatusCodes.Good;
1390
+ propertyNode.$dataValue.value.value = null;
1391
+ return new DataValue(propertyNode.$dataValue);
1336
1392
  }
1337
- const value = prepareVariantValue(dataTypeNodeId, prop);
1338
- propertyNode._dataValue.statusCode = StatusCodes.Good;
1339
- propertyNode._dataValue.value.value = value;
1340
- return new DataValue(propertyNode._dataValue);
1393
+ const value = prepareVariantValue(dataType, propertyValue);
1394
+ propertyNode.$dataValue.statusCode = StatusCodes.Good;
1395
+ propertyNode.$dataValue.value.dataType = dataType;
1396
+ propertyNode.$dataValue.value.value = value;
1397
+ return new DataValue(propertyNode.$dataValue);
1341
1398
  },
1342
1399
  timestamped_set: (dataValue: DataValue, callback: CallbackT<StatusCode>) => {
1343
1400
  dataValue;
@@ -1355,8 +1412,8 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1355
1412
  const s = this.readValue();
1356
1413
  // istanbul ignore next
1357
1414
  if (this.dataTypeObj.isAbstract) {
1358
- console.log("Warning the DataType associated with this Variable is abstract ", this.dataTypeObj.browseName.toString());
1359
- console.log("You need to provide a extension object yourself ");
1415
+ warningLog("Warning the DataType associated with this Variable is abstract ", this.dataTypeObj.browseName.toString());
1416
+ warningLog("You need to provide a extension object yourself ");
1360
1417
  throw new Error("bindExtensionObject requires a extensionObject as associated dataType is only abstract");
1361
1418
  }
1362
1419
  if (s.value && (s.value.dataType === DataType.Null || (s.value.dataType === DataType.ExtensionObject && !s.value.value))) {
@@ -1376,8 +1433,8 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1376
1433
  this.bindVariable(
1377
1434
  {
1378
1435
  timestamped_get() {
1379
- self._dataValue.value.value = self.$extensionObject;
1380
- const d = new DataValue(self._dataValue);
1436
+ self.$dataValue.value.value = self.$extensionObject;
1437
+ const d = new DataValue(self.$dataValue);
1381
1438
  d.value = new Variant(d.value);
1382
1439
  return d;
1383
1440
  },
@@ -1386,7 +1443,6 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1386
1443
  if (!self.checkExtensionObjectIsCorrect(ext)) {
1387
1444
  return callback(null, StatusCodes.BadInvalidArgument);
1388
1445
  }
1389
-
1390
1446
  self.$extensionObject = new Proxy(ext, makeHandler(self));
1391
1447
  self.touchValue();
1392
1448
  callback(null, StatusCodes.Good);
@@ -1402,51 +1458,65 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1402
1458
  assert(s.statusCode.equals(StatusCodes.Good));
1403
1459
  }
1404
1460
 
1405
- let property: any;
1406
- let camelCaseName: any;
1407
1461
  // ------------------------------------------------------
1408
1462
  // now bind each member
1409
1463
  // ------------------------------------------------------
1410
- const definition = dt._getDefinition(false) as StructureDefinition | null;
1464
+ const definition = dt._getDefinition() as StructureDefinition | null;
1411
1465
 
1412
1466
  // istanbul ignore next
1413
1467
  if (!definition) {
1414
1468
  throw new Error("xx definition missing in " + dt.toString());
1415
1469
  }
1416
- for (const field of definition.fields || []) {
1417
- camelCaseName = lowerFirstLetter(field.name!);
1418
- const component = components.filter((f) => f.browseName.name!.toString() === field.name);
1419
- if (component.length === 1) {
1420
- property = component[0];
1470
+
1471
+ const getOrCreateProperty = (field: StructureField): UAVariableImpl => {
1472
+ let property: UAVariableImpl;
1473
+ const selectedComponents = components.filter(
1474
+ (f) => f instanceof UAVariableImpl && f.browseName.name!.toString() === field.name
1475
+ );
1476
+
1477
+ if (field.dataType.value === DataType.Variant) {
1478
+ warningLog("Warning : variant is not supported in ExtensionObject");
1479
+ }
1480
+ if (selectedComponents.length === 1) {
1481
+ property = selectedComponents[0] as UAVariableImpl;
1421
1482
  /* istanbul ignore next */
1422
1483
  } else {
1484
+ debugLog("adding missing array variable", field.name, this.browseName.toString(), this.nodeId.toString());
1423
1485
  // todo: Handle array appropriately...
1424
- assert(component.length === 0);
1486
+ assert(selectedComponents.length === 0);
1425
1487
  // create a variable (Note we may use ns=1;s=parentName/0:PropertyName)
1426
1488
  property = this.namespace.addVariable({
1427
1489
  browseName: { namespaceIndex: structureNamespace, name: field.name!.toString() },
1428
1490
  componentOf: this,
1429
1491
  dataType: field.dataType,
1430
1492
  minimumSamplingInterval: this.minimumSamplingInterval
1431
- });
1493
+ }) as UAVariableImpl;
1432
1494
  assert(property.minimumSamplingInterval === this.minimumSamplingInterval);
1433
1495
  }
1496
+ return property;
1497
+ };
1434
1498
 
1435
- property._dataValue.statusCode = StatusCodes.Good;
1436
- property.touchValue();
1437
-
1499
+ for (const field of definition.fields || []) {
1438
1500
  if (NodeId.sameNodeId(NodeId.nullNodeId, field.dataType)) {
1439
- debugLog("field.dataType is null ! " + field.name + " " + field.description?.text);
1440
- debugLog(" dataType replaced with BaseDataType ");
1501
+ warningLog("field.dataType is null ! ", field.toString(), NodeId.nullNodeId.toString());
1502
+ warningLog(" dataType replaced with BaseDataType ");
1503
+ warningLog(definition.toString());
1441
1504
  field.dataType = this.resolveNodeId("BaseDataType");
1442
1505
  }
1443
- const dataTypeNodeId = addressSpace.findCorrespondingBasicDataType(field.dataType);
1506
+
1507
+ const camelCaseName = lowerFirstLetter(field.name!);
1444
1508
  assert(Object.prototype.hasOwnProperty.call(this.$extensionObject, camelCaseName));
1445
1509
 
1510
+ const propertyNode = getOrCreateProperty(field);
1511
+
1512
+ propertyNode.$dataValue.statusCode = StatusCodes.Good;
1513
+ propertyNode.touchValue();
1514
+
1515
+ const basicDataType = addressSpace.findCorrespondingBasicDataType(field.dataType);
1516
+
1446
1517
  // istanbul ignore next
1447
1518
  if (doDebug) {
1448
1519
  const x = addressSpace.findNode(field.dataType)!.browseName.toString();
1449
- const basicType = addressSpace.findCorrespondingBasicDataType(field.dataType);
1450
1520
  debugLog(
1451
1521
  chalk.cyan("xxx"),
1452
1522
  " dataType",
@@ -1456,52 +1526,56 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1456
1526
  chalk.cyan(w(valueRankToString(field.valueRank), 10)),
1457
1527
  chalk.green(w(x, 25)),
1458
1528
  "basicType = ",
1459
- chalk.yellow(w(basicType.toString(), 20)),
1460
- property.nodeId.toString(),
1461
- property.readValue().statusCode.toString()
1529
+ chalk.yellow(w(basicDataType.toString(), 20)),
1530
+ propertyNode.nodeId.toString(),
1531
+ propertyNode.readValue().statusCode.toString()
1462
1532
  );
1463
1533
  }
1464
- if (this.$extensionObject[camelCaseName] !== undefined && dataTypeNodeId === DataType.ExtensionObject) {
1534
+ if (this.$extensionObject[camelCaseName] !== undefined && basicDataType === DataType.ExtensionObject) {
1465
1535
  assert(this.$extensionObject[camelCaseName] instanceof Object);
1466
- this.$extensionObject[camelCaseName] = new Proxy(this.$extensionObject[camelCaseName], makeHandler(property));
1467
- property._dataValue.value = new Variant({
1468
- dataType: DataType.ExtensionObject,
1469
- value: this.$extensionObject[camelCaseName]
1470
- });
1471
- property.bindExtensionObject();
1472
- property.$extensionObject = this.$extensionObject[camelCaseName];
1536
+ this.$extensionObject[camelCaseName] = new Proxy(this.$extensionObject[camelCaseName], makeHandler(propertyNode));
1537
+
1538
+ propertyNode._internal_set_value(
1539
+ new Variant({
1540
+ dataType: DataType.ExtensionObject,
1541
+ value: this.$extensionObject[camelCaseName]
1542
+ })
1543
+ );
1544
+
1545
+ propertyNode.bindExtensionObject();
1546
+ propertyNode.$extensionObject = this.$extensionObject[camelCaseName];
1473
1547
  } else {
1474
- const dataTypeAsString = DataType[dataTypeNodeId];
1475
- assert(typeof dataTypeAsString === "string");
1476
1548
  const prop = this.$extensionObject[camelCaseName];
1477
-
1478
1549
  if (prop === undefined) {
1479
- property._dataValue.value = new Variant({
1480
- dataType: DataType.Null
1481
- });
1550
+ propertyNode._internal_set_value(
1551
+ new Variant({
1552
+ dataType: DataType.Null
1553
+ })
1554
+ );
1482
1555
  } else {
1483
- const preparedValue = prepareVariantValue(dataTypeNodeId, prop);
1484
- property._dataValue.value = new Variant({
1485
- dataType: dataTypeAsString,
1486
- value: preparedValue
1487
- });
1556
+ const preparedValue = prepareVariantValue(basicDataType, prop);
1557
+ propertyNode._internal_set_value(
1558
+ new Variant({
1559
+ dataType: basicDataType,
1560
+ value: preparedValue
1561
+ })
1562
+ );
1488
1563
  }
1489
1564
 
1490
1565
  // eslint-disable-next-line @typescript-eslint/no-this-alias
1491
1566
  const self = this;
1492
- property.camelCaseName = camelCaseName;
1493
- property.setValueFromSource = function (this: any, variant: VariantLike) {
1567
+ // property.camelCaseName = camelCaseName;
1568
+ propertyNode.setValueFromSource = function (this: UAVariableImpl, variant: VariantLike) {
1494
1569
  // eslint-disable-next-line @typescript-eslint/no-this-alias
1495
1570
  const inner_this = this;
1496
- variant = Variant.coerce(variant);
1497
- // xx console.log("PropertySetValueFromSource this", inner_this.nodeId.toString(), inner_this.browseName.toString(), variant.toString(), inner_this.dataType.toString());
1498
- // xx assert(variant.dataType === this.dataType);
1499
- self.$extensionObject[inner_this.camelCaseName] = variant.value;
1571
+ const variant1 = Variant.coerce(variant);
1572
+ inner_this.verifyVariantCompatibility(variant1);
1573
+ self.$extensionObject[camelCaseName] = variant1.value;
1500
1574
  self.touchValue();
1501
1575
  };
1502
1576
  }
1503
- assert(property.readValue().statusCode.equals(StatusCodes.Good));
1504
- bindProperty(property, camelCaseName, this.$extensionObject, dataTypeNodeId);
1577
+ assert(propertyNode.readValue().statusCode.equals(StatusCodes.Good));
1578
+ bindProperty(propertyNode, camelCaseName, this.$extensionObject, basicDataType);
1505
1579
  }
1506
1580
  assert(this.$extensionObject instanceof Object);
1507
1581
  return this.$extensionObject;
@@ -1543,7 +1617,6 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1543
1617
  c1[name] = c2[name];
1544
1618
  c1[name] += 1;
1545
1619
 
1546
- // xx console.log(partialData);
1547
1620
  setExtensionObjectValue(this, partialData);
1548
1621
  }
1549
1622
 
@@ -1687,10 +1760,16 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1687
1760
  return validateDataType(this.addressSpace, this.dataType, variantDataType, this.nodeId, /* allow Nulls */ false);
1688
1761
  }
1689
1762
 
1763
+ public _internal_set_value(value: Variant): void {
1764
+ if (value.dataType !== DataType.Null) {
1765
+ this.verifyVariantCompatibility(value);
1766
+ }
1767
+ this.$dataValue.value = value;
1768
+ }
1690
1769
  public _internal_set_dataValue(dataValue: DataValue, indexRange?: NumericRange | null): void {
1691
1770
  assert(dataValue, "expecting a dataValue");
1692
1771
  assert(dataValue instanceof DataValue, "expecting dataValue to be a DataValue");
1693
- assert(dataValue !== this._dataValue, "expecting dataValue to be different from previous DataValue instance");
1772
+ assert(dataValue !== this.$dataValue, "expecting dataValue to be different from previous DataValue instance");
1694
1773
 
1695
1774
  if (dataValue.value.arrayType === VariantArrayType.Matrix) {
1696
1775
  if (!dataValue.value.dimensions) {
@@ -1714,27 +1793,31 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1714
1793
  // istanbul ignore next
1715
1794
  if (this.dataType.namespace === 0) {
1716
1795
  if (this.dataType.value === DataType.LocalizedText && dataValue.value.dataType !== DataType.LocalizedText) {
1717
- throw new Error("Invalid dataValue provided (expecting a LocalizedText) but got " + dataValue.toString());
1796
+ const message = "Invalid dataValue provided (expecting a LocalizedText) but got " + dataValue.toString();
1797
+ errorLog(message);
1798
+ throw new Error(message);
1718
1799
  }
1719
1800
  }
1720
1801
 
1721
- const old_dataValue = this._dataValue;
1802
+ this.verifyVariantCompatibility(dataValue.value);
1722
1803
 
1723
- this._dataValue = dataValue;
1724
- this._dataValue.statusCode = this._dataValue.statusCode || StatusCodes.Good;
1804
+ const old_dataValue = this.$dataValue;
1805
+
1806
+ this.$dataValue = dataValue;
1807
+ this.$dataValue.statusCode = this.$dataValue.statusCode || StatusCodes.Good;
1725
1808
 
1726
1809
  // repair missing timestamps
1727
1810
  if (!dataValue.serverTimestamp) {
1728
- this._dataValue.serverTimestamp = old_dataValue.serverTimestamp;
1729
- this._dataValue.serverPicoseconds = old_dataValue.serverPicoseconds;
1811
+ this.$dataValue.serverTimestamp = old_dataValue.serverTimestamp;
1812
+ this.$dataValue.serverPicoseconds = old_dataValue.serverPicoseconds;
1730
1813
  }
1731
1814
  if (!dataValue.sourceTimestamp) {
1732
- this._dataValue.sourceTimestamp = old_dataValue.sourceTimestamp;
1733
- this._dataValue.sourcePicoseconds = old_dataValue.sourcePicoseconds;
1815
+ this.$dataValue.sourceTimestamp = old_dataValue.sourceTimestamp;
1816
+ this.$dataValue.sourcePicoseconds = old_dataValue.sourcePicoseconds;
1734
1817
  }
1735
1818
 
1736
1819
  if (!sameDataValue(old_dataValue, dataValue)) {
1737
- this.emit("value_changed", this._dataValue, indexRange);
1820
+ this.emit("value_changed", this.$dataValue, indexRange);
1738
1821
  }
1739
1822
  }
1740
1823
 
@@ -1930,10 +2013,10 @@ function _calculateEffectiveUserAccessLevelFromPermission(
1930
2013
  }
1931
2014
  }
1932
2015
 
1933
- function adjustVariant2(this: UAVariable, variant: Variant): Variant {
2016
+ function adjustVariant2(this: UAVariableImpl, variant: Variant): Variant {
1934
2017
  // convert Variant( Scalar|ByteString) => Variant(Array|ByteArray)
1935
2018
  const addressSpace = this.addressSpace;
1936
- const basicType = addressSpace.findCorrespondingBasicDataType(this.dataType);
2019
+ const basicType = this.getBasicDataType();
1937
2020
  variant = adjustVariant(variant, this.valueRank, basicType);
1938
2021
  return variant;
1939
2022
  }
@@ -1950,7 +2033,6 @@ function _default_writable_timestamped_set_func(
1950
2033
  dataValue: DataValue,
1951
2034
  callback: (err: Error | null, statusCode: StatusCode, dataValue?: DataValue | null) => void
1952
2035
  ) {
1953
- /* jshint validthis: true */
1954
2036
  assert(dataValue instanceof DataValue);
1955
2037
  callback(null, StatusCodes.Good, dataValue);
1956
2038
  }
@@ -1992,7 +2074,7 @@ function _Variable_bind_with_async_refresh(this: UAVariableImpl, options: any) {
1992
2074
  this.refreshFunc = options.refreshFunc;
1993
2075
 
1994
2076
  // assert(this.readValue().statusCode === StatusCodes.BadNodeIdUnknown);
1995
- this._dataValue.statusCode = StatusCodes.UncertainInitialValue;
2077
+ this.$dataValue.statusCode = StatusCodes.UncertainInitialValue;
1996
2078
 
1997
2079
  // TO DO : REVISIT THIS ASSUMPTION
1998
2080
  if (false && this.minimumSamplingInterval === 0) {
@@ -2004,7 +2086,13 @@ function _Variable_bind_with_async_refresh(this: UAVariableImpl, options: any) {
2004
2086
  }
2005
2087
 
2006
2088
  // variation 2
2007
- function _Variable_bind_with_timestamped_get(this: UAVariableImpl, options: any) {
2089
+ function _Variable_bind_with_timestamped_get(
2090
+ this: UAVariableImpl,
2091
+ options: {
2092
+ get: undefined;
2093
+ timestamped_get: TimestampGetFunc;
2094
+ }
2095
+ ) {
2008
2096
  /* jshint validthis: true */
2009
2097
  assert(this instanceof UAVariableImpl);
2010
2098
  assert(typeof options.timestamped_get === "function");
@@ -2012,17 +2100,20 @@ function _Variable_bind_with_timestamped_get(this: UAVariableImpl, options: any)
2012
2100
  assert(!this._timestamped_get_func);
2013
2101
 
2014
2102
  const async_refresh_func = (callback: (err: Error | null, dataValue?: DataValue) => void) => {
2015
- Promise.resolve((this._timestamped_get_func! as TimestampGetFunction1).call(this))
2103
+ Promise.resolve((this._timestamped_get_func! as VariableDataValueGetterSync).call(this))
2016
2104
  .then((dataValue) => callback(null, dataValue))
2017
- .catch((err) => callback(err as Error));
2105
+ .catch((err) => {
2106
+ errorLog("asyncRefresh error: Variable is ", this.nodeId.toString(), this.browseName.toString());
2107
+ callback(err as Error);
2108
+ });
2018
2109
  };
2019
-
2110
+ const pThis = this as UAVariable;
2020
2111
  if (options.timestamped_get.length === 0) {
2021
- const timestamped_get = options.timestamped_get as TimestampGetFunction1;
2112
+ const timestamped_get = options.timestamped_get as (this: UAVariable) => DataValue | Promise<DataValue>;
2022
2113
  // sync version | Promise version
2023
2114
  this._timestamped_get_func = timestamped_get;
2024
2115
 
2025
- const dataValue_verify = timestamped_get!.call(this);
2116
+ const dataValue_verify = timestamped_get.call(pThis);
2026
2117
  // dataValue_verify should be a DataValue or a Promise
2027
2118
  /* istanbul ignore next */
2028
2119
  if (!(dataValue_verify instanceof DataValue) && typeof dataValue_verify.then !== "function") {
@@ -2045,11 +2136,11 @@ function _Variable_bind_with_timestamped_get(this: UAVariableImpl, options: any)
2045
2136
  }
2046
2137
 
2047
2138
  // variation 1
2048
- function _Variable_bind_with_simple_get(this: UAVariableImpl, options: any) {
2139
+ function _Variable_bind_with_simple_get(this: UAVariableImpl, options: GetterOptions) {
2049
2140
  /* jshint validthis: true */
2050
2141
  assert(this instanceof UAVariableImpl);
2051
2142
  assert(typeof options.get === "function", "should specify get function");
2052
- assert(options.get.length === 0, "get function should not have arguments");
2143
+ assert(options.get!.length === 0, "get function should not have arguments");
2053
2144
  assert(!options.timestamped_get, "should not specify a timestamped_get function when get is specified");
2054
2145
  assert(!this._timestamped_get_func);
2055
2146
  assert(!this._get_func);
@@ -2073,16 +2164,15 @@ function _Variable_bind_with_simple_get(this: UAVariableImpl, options: any) {
2073
2164
  if (is_StatusCode(value)) {
2074
2165
  return new DataValue({ statusCode: value });
2075
2166
  } else {
2076
- if (!this._dataValue || !isGoodish(this._dataValue.statusCode) || !sameVariant(this._dataValue.value, value)) {
2167
+ if (!this.$dataValue || !isGoodish(this.$dataValue.statusCode) || !sameVariant(this.$dataValue.value, value)) {
2077
2168
  this.setValueFromSource(value, StatusCodes.Good);
2078
- } else {
2079
- // XX console.log("YYYYYYYYYYYYYYYYYYYYYYYYYY",this.browseName.toString());
2080
2169
  }
2081
- return this._dataValue;
2170
+ return this.$dataValue;
2082
2171
  }
2083
2172
  };
2084
2173
 
2085
2174
  _Variable_bind_with_timestamped_get.call(this, {
2175
+ get: undefined,
2086
2176
  timestamped_get: timestamped_get_func_from__Variable_bind_with_simple_get
2087
2177
  });
2088
2178
  }
@@ -2100,16 +2190,16 @@ function _Variable_bind_with_simple_set(this: UAVariableImpl, options: any) {
2100
2190
 
2101
2191
  this._timestamped_set_func = (
2102
2192
  timestamped_value: DataValue,
2103
- indexRange: NumericRange,
2104
2193
  callback: (err: Error | null, statusCode: StatusCode, dataValue: DataValue) => void
2105
2194
  ) => {
2106
2195
  assert(timestamped_value instanceof DataValue);
2107
2196
  this._set_func(timestamped_value.value, (err: Error | null, statusCode: StatusCode) => {
2197
+ // istanbul ignore next
2108
2198
  if (!err && !statusCode) {
2109
- console.log(
2199
+ errorLog(
2110
2200
  chalk.red("UAVariable Binding Error _set_func must return a StatusCode, check the bindVariable parameters")
2111
2201
  );
2112
- console.log(chalk.yellow("StatusCode.Good is assumed"));
2202
+ errorLog(chalk.yellow("StatusCode.Good is assumed"));
2113
2203
  return callback(err, StatusCodes.Good, timestamped_value);
2114
2204
  }
2115
2205
  callback(err, statusCode, timestamped_value);
@@ -2117,22 +2207,20 @@ function _Variable_bind_with_simple_set(this: UAVariableImpl, options: any) {
2117
2207
  };
2118
2208
  }
2119
2209
 
2120
- function _Variable_bind_with_timestamped_set(this: UAVariableImpl, options: any) {
2121
- assert(this instanceof UAVariableImpl);
2210
+ function _Variable_bind_with_timestamped_set(
2211
+ this: UAVariableImpl,
2212
+ options: {
2213
+ timestamped_set: TimestampSetFunc;
2214
+ set: undefined;
2215
+ }
2216
+ ) {
2122
2217
  assert(typeof options.timestamped_set === "function");
2123
2218
  assert(
2124
2219
  options.timestamped_set.length === 2,
2125
2220
  "timestamped_set must have 2 parameters timestamped_set: function(dataValue,callback){}"
2126
2221
  );
2127
2222
  assert(!options.set, "should not specify set when timestamped_set_func exists ");
2128
- this._timestamped_set_func = (
2129
- dataValue: DataValue,
2130
- indexRange: NumericRange,
2131
- callback: (err: Error | null, statusCode: StatusCode, dataValue: DataValue) => void
2132
- ) => {
2133
- // xx assert(!indexRange,"indexRange Not Implemented");
2134
- return options.timestamped_set.call(this, dataValue, callback);
2135
- };
2223
+ this._timestamped_set_func = convertToCallbackFunction1<StatusCode, DataValue, UAVariable>(options.timestamped_set);
2136
2224
  }
2137
2225
 
2138
2226
  interface SetterOptions {
@@ -2147,15 +2235,20 @@ function bind_setter(this: UAVariableImpl, options: SetterOptions) {
2147
2235
  } else if (typeof options.timestamped_set === "function") {
2148
2236
  // variation 2
2149
2237
  assert(typeof options.timestamped_get === "function", "timestamped_set must be used with timestamped_get ");
2150
- _Variable_bind_with_timestamped_set.call(this, options);
2238
+ _Variable_bind_with_timestamped_set.call(this, {
2239
+ set: undefined,
2240
+ timestamped_set: options.timestamped_set
2241
+ });
2151
2242
  } else if (typeof options.timestamped_get === "function") {
2152
2243
  // timestamped_get is specified but timestamped_set is not
2153
2244
  // => Value is read-only
2154
2245
  _Variable_bind_with_timestamped_set.call(this, {
2246
+ set: undefined,
2155
2247
  timestamped_set: _not_writable_timestamped_set_func
2156
2248
  });
2157
2249
  } else {
2158
2250
  _Variable_bind_with_timestamped_set.call(this, {
2251
+ set: undefined,
2159
2252
  timestamped_set: _default_writable_timestamped_set_func
2160
2253
  });
2161
2254
  }
@@ -2164,7 +2257,7 @@ function bind_setter(this: UAVariableImpl, options: SetterOptions) {
2164
2257
  interface GetterOptions {
2165
2258
  get?: GetFunc;
2166
2259
  timestamped_get?: TimestampGetFunc;
2167
- refreshFunc?: any;
2260
+ refreshFunc?: (callback: CallbackT<DataValue>) => void;
2168
2261
  dataType?: DataType | string;
2169
2262
  value?: any;
2170
2263
  }
@@ -2174,7 +2267,10 @@ function bind_getter(this: UAVariableImpl, options: GetterOptions) {
2174
2267
  _Variable_bind_with_simple_get.call(this, options);
2175
2268
  } else if (typeof options.timestamped_get === "function") {
2176
2269
  // variation 2
2177
- _Variable_bind_with_timestamped_get.call(this, options);
2270
+ _Variable_bind_with_timestamped_get.call(this, {
2271
+ get: undefined,
2272
+ timestamped_get: options.timestamped_get
2273
+ });
2178
2274
  } else if (typeof options.refreshFunc === "function") {
2179
2275
  // variation 3
2180
2276
  _Variable_bind_with_async_refresh.call(this, options);