node-opcua-address-space-for-conformance-testing 2.167.0 → 2.169.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/address_space_for_conformance_testing.d.ts +6 -2
  2. package/dist/address_space_for_conformance_testing.js +27 -1306
  3. package/dist/address_space_for_conformance_testing.js.map +1 -1
  4. package/dist/conformance_testing/access_right_variables.d.ts +5 -0
  5. package/dist/conformance_testing/access_right_variables.js +45 -0
  6. package/dist/conformance_testing/access_right_variables.js.map +1 -0
  7. package/dist/conformance_testing/analog_data_items.d.ts +5 -0
  8. package/dist/conformance_testing/analog_data_items.js +117 -0
  9. package/dist/conformance_testing/analog_data_items.js.map +1 -0
  10. package/dist/conformance_testing/discrete_variables.d.ts +4 -0
  11. package/dist/conformance_testing/discrete_variables.js +111 -0
  12. package/dist/conformance_testing/discrete_variables.js.map +1 -0
  13. package/dist/conformance_testing/enumeration_variable.d.ts +5 -0
  14. package/dist/conformance_testing/enumeration_variable.js +24 -0
  15. package/dist/conformance_testing/enumeration_variable.js.map +1 -0
  16. package/dist/conformance_testing/helpers.d.ts +13 -0
  17. package/dist/conformance_testing/helpers.js +204 -0
  18. package/dist/conformance_testing/helpers.js.map +1 -0
  19. package/dist/conformance_testing/mass_variables.d.ts +5 -0
  20. package/dist/conformance_testing/mass_variables.js +31 -0
  21. package/dist/conformance_testing/mass_variables.js.map +1 -0
  22. package/dist/conformance_testing/node_with_references.d.ts +5 -0
  23. package/dist/conformance_testing/node_with_references.js +119 -0
  24. package/dist/conformance_testing/node_with_references.js.map +1 -0
  25. package/dist/conformance_testing/object_with_methods.d.ts +5 -0
  26. package/dist/conformance_testing/object_with_methods.js +85 -0
  27. package/dist/conformance_testing/object_with_methods.js.map +1 -0
  28. package/dist/conformance_testing/path_and_large_arrays.d.ts +6 -0
  29. package/dist/conformance_testing/path_and_large_arrays.js +33 -0
  30. package/dist/conformance_testing/path_and_large_arrays.js.map +1 -0
  31. package/dist/conformance_testing/sample_view.d.ts +5 -0
  32. package/dist/conformance_testing/sample_view.js +25 -0
  33. package/dist/conformance_testing/sample_view.js.map +1 -0
  34. package/dist/conformance_testing/simulation_variables.d.ts +5 -0
  35. package/dist/conformance_testing/simulation_variables.js +113 -0
  36. package/dist/conformance_testing/simulation_variables.js.map +1 -0
  37. package/dist/conformance_testing/static_variables.d.ts +2 -0
  38. package/dist/conformance_testing/static_variables.js +70 -0
  39. package/dist/conformance_testing/static_variables.js.map +1 -0
  40. package/dist/conformance_testing/trigger_nodes.d.ts +5 -0
  41. package/dist/conformance_testing/trigger_nodes.js +41 -0
  42. package/dist/conformance_testing/trigger_nodes.js.map +1 -0
  43. package/dist/conformance_testing/type_defaults.d.ts +54 -0
  44. package/dist/conformance_testing/type_defaults.js +96 -0
  45. package/dist/conformance_testing/type_defaults.js.map +1 -0
  46. package/package.json +12 -12
  47. package/src/address_space_for_conformance_testing.ts +34 -1494
  48. package/src/conformance_testing/access_right_variables.ts +55 -0
  49. package/src/conformance_testing/analog_data_items.ts +136 -0
  50. package/src/conformance_testing/discrete_variables.ts +131 -0
  51. package/src/conformance_testing/enumeration_variable.ts +27 -0
  52. package/src/conformance_testing/helpers.ts +226 -0
  53. package/src/conformance_testing/mass_variables.ts +42 -0
  54. package/src/conformance_testing/node_with_references.ts +130 -0
  55. package/src/conformance_testing/object_with_methods.ts +93 -0
  56. package/src/conformance_testing/path_and_large_arrays.ts +35 -0
  57. package/src/conformance_testing/sample_view.ts +28 -0
  58. package/src/conformance_testing/simulation_variables.ts +129 -0
  59. package/src/conformance_testing/static_variables.ts +71 -0
  60. package/src/conformance_testing/trigger_nodes.ts +49 -0
  61. package/src/conformance_testing/type_defaults.ts +60 -0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Access-right test variables for conformance testing.
3
+ */
4
+ import type { Namespace, UAObject } from "node-opcua-address-space";
5
+ import { makeAccessLevelFlag } from "node-opcua-data-model";
6
+ import { DataType, Variant, VariantArrayType } from "node-opcua-variant";
7
+
8
+ const defaultInt32Value = new Variant({
9
+ dataType: DataType.Int32,
10
+ arrayType: VariantArrayType.Scalar,
11
+ value: 36
12
+ });
13
+
14
+ const accessRightConfigs: {
15
+ name: string;
16
+ accessLevel: string;
17
+ userAccessLevel: string;
18
+ value?: Record<string, unknown>;
19
+ }[] = [
20
+ { name: "AccessLevel_CurrentRead", accessLevel: "CurrentRead", userAccessLevel: "CurrentRead" },
21
+ { name: "AccessLevel_CurrentWrite", accessLevel: "CurrentWrite", userAccessLevel: "CurrentWrite", value: {} },
22
+ { name: "AccessLevel_CurrentRead_NotUser", accessLevel: "CurrentRead", userAccessLevel: "" },
23
+ { name: "AccessLevel_CurrentWrite_NotUser", accessLevel: "CurrentWrite | CurrentRead", userAccessLevel: "CurrentRead" },
24
+ { name: "AccessLevel_CurrentRead_NotCurrentWrite", accessLevel: "CurrentRead", userAccessLevel: "CurrentRead" },
25
+ { name: "AccessLevel_CurrentWrite_NotCurrentRead", accessLevel: "CurrentWrite", userAccessLevel: "CurrentWrite" },
26
+ { name: "AccessLevel_DeniedAll", accessLevel: "", userAccessLevel: "" }
27
+ ];
28
+
29
+ export function addAccessRightVariables(namespace: Namespace, parentFolder: UAObject): void {
30
+ const accessRight_Folder = namespace.addFolder(parentFolder, {
31
+ browseName: "AccessRight",
32
+ description: "Folder containing various nodes with different access right behavior",
33
+ nodeId: "s=AccessRight"
34
+ });
35
+
36
+ const accessLevel_All_Folder = namespace.addFolder(accessRight_Folder, {
37
+ browseName: "AccessLevel",
38
+ description: "Various node with different access right behavior",
39
+ nodeId: "s=AccessLevel"
40
+ });
41
+
42
+ for (const config of accessRightConfigs) {
43
+ namespace.addVariable({
44
+ componentOf: accessLevel_All_Folder,
45
+ browseName: config.name,
46
+ description: { locale: "en", text: config.name },
47
+ nodeId: `s=${config.name}`,
48
+ dataType: "Int32",
49
+ valueRank: -1,
50
+ accessLevel: makeAccessLevelFlag(config.accessLevel),
51
+ userAccessLevel: makeAccessLevelFlag(config.userAccessLevel),
52
+ value: config.value ?? defaultInt32Value
53
+ });
54
+ }
55
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Analog data items, data items, and array analog data items for conformance testing.
3
+ */
4
+ import type { Namespace, UAObject } from "node-opcua-address-space";
5
+ import { assert } from "node-opcua-assert";
6
+ import { standardUnits } from "node-opcua-data-access";
7
+ import { DataType, VariantArrayType } from "node-opcua-variant";
8
+
9
+ interface RangeOptions {
10
+ low: number;
11
+ high: number;
12
+ }
13
+
14
+ function makeRange(dataType: DataType): { engineeringUnitsRange: RangeOptions; instrumentRange: RangeOptions } {
15
+ let engineeringUnitsRange = { low: -200, high: 200 };
16
+ let instrumentRange = { low: -200, high: 200 };
17
+ if (DataType[dataType][0] === "U" || dataType === DataType.Byte) {
18
+ engineeringUnitsRange = { low: 10, high: 250 };
19
+ instrumentRange = { low: 10, high: 250 };
20
+ }
21
+ return { engineeringUnitsRange, instrumentRange };
22
+ }
23
+
24
+ export function addAnalogDataItems(namespace: Namespace, parentFolder: UAObject): void {
25
+ function _addDataItem(localParentFolder: UAObject, dataType: DataType, initialValue: unknown): void {
26
+ const name = `${DataType[dataType]}DataItem`;
27
+ const nodeId = `s=${name}`;
28
+
29
+ const v = namespace.addDataItem({
30
+ componentOf: localParentFolder,
31
+ nodeId,
32
+ browseName: name,
33
+ definition: "(tempA -25) + tempB",
34
+ dataType,
35
+ value: {
36
+ arrayType: VariantArrayType.Scalar,
37
+ dataType,
38
+ value: initialValue
39
+ }
40
+ });
41
+ assert(v.nodeId.toString() === `ns=${namespace.index};${nodeId}`, `ns=${namespace.index};${nodeId} ${v.nodeId.toString()}`);
42
+ }
43
+
44
+ function _addAnalogDataItem(localParentFolder: UAObject, dataType: DataType, initialValue: unknown): void {
45
+ const { engineeringUnitsRange, instrumentRange } = makeRange(dataType);
46
+ assert(
47
+ Array.isArray(initialValue) ||
48
+ ((initialValue as number) >= engineeringUnitsRange.low && (initialValue as number) <= engineeringUnitsRange.high)
49
+ );
50
+ const name = `${DataType[dataType]}AnalogDataItem`;
51
+ const nodeId = `s=${name}`;
52
+
53
+ namespace.addAnalogDataItem({
54
+ componentOf: localParentFolder,
55
+ nodeId,
56
+ browseName: name,
57
+ definition: "...",
58
+ valuePrecision: 0.5,
59
+ engineeringUnitsRange,
60
+ instrumentRange,
61
+ engineeringUnits: standardUnits.degree_celsius,
62
+ dataType,
63
+ value: { arrayType: VariantArrayType.Scalar, dataType, value: initialValue }
64
+ });
65
+ }
66
+
67
+ function _addArrayAnalogDataItem(localParentFolder: UAObject, dataType: DataType, initialValue: unknown) {
68
+ const name = `${DataType[dataType]}ArrayAnalogDataItem`;
69
+ const nodeId = `s=${name}`;
70
+ const { engineeringUnitsRange, instrumentRange } = makeRange(dataType);
71
+
72
+ namespace.addAnalogDataItem({
73
+ componentOf: localParentFolder,
74
+ nodeId,
75
+ browseName: name,
76
+ definition: "(tempA -25) + tempB",
77
+ valuePrecision: 0.5,
78
+ engineeringUnitsRange,
79
+ instrumentRange,
80
+ engineeringUnits: standardUnits.degree_celsius,
81
+ dataType,
82
+ value: {
83
+ arrayType: VariantArrayType.Array,
84
+ dataType,
85
+ value: [initialValue, initialValue, initialValue, initialValue, initialValue]
86
+ }
87
+ });
88
+ }
89
+
90
+ const analogItemFolder = namespace.addObject({
91
+ organizedBy: parentFolder,
92
+ browseName: "Simulation_AnalogDataItem",
93
+ typeDefinition: "FolderType",
94
+ nodeId: "s=Simulation_AnalogDataItem"
95
+ });
96
+
97
+ const { engineeringUnitsRange, instrumentRange } = makeRange(DataType.Double);
98
+ namespace.addAnalogDataItem({
99
+ componentOf: analogItemFolder,
100
+ nodeId: "s=DoubleAnalogDataItemWithEU",
101
+ browseName: "DoubleAnalogDataItemWithEU",
102
+ definition: "(tempA -25) + tempB",
103
+ valuePrecision: 0.5,
104
+ engineeringUnitsRange,
105
+ instrumentRange,
106
+ engineeringUnits: standardUnits.degree_celsius,
107
+ dataType: DataType.Double,
108
+ value: { dataType: DataType.Double, value: 19.5 }
109
+ });
110
+
111
+ const data = [
112
+ { dataType: DataType.Double, value: 3.14 },
113
+ { dataType: DataType.Float, value: 3.14 },
114
+ { dataType: DataType.Int16, value: -10 },
115
+ { dataType: DataType.UInt16, value: 10 },
116
+ { dataType: DataType.Int32, value: -100 },
117
+ { dataType: DataType.UInt32, value: 100 },
118
+ { dataType: DataType.Int64, value: [0, 0] },
119
+ { dataType: DataType.UInt64, value: [0, 0] },
120
+ { dataType: DataType.Byte, value: 65 },
121
+ { dataType: DataType.SByte, value: -23 }
122
+ ];
123
+
124
+ for (const e of data) {
125
+ _addAnalogDataItem(analogItemFolder, e.dataType, e.value);
126
+ }
127
+ for (const e of data) {
128
+ _addDataItem(analogItemFolder, e.dataType, e.value);
129
+ }
130
+ _addDataItem(analogItemFolder, DataType.String, "some string");
131
+ _addDataItem(analogItemFolder, DataType.DateTime, new Date());
132
+
133
+ for (const e of data) {
134
+ _addArrayAnalogDataItem(analogItemFolder, e.dataType, e.value);
135
+ }
136
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Discrete variable types: TwoStateDiscrete, MultiStateDiscrete, MultiStateValueDiscrete.
3
+ */
4
+ import type { Int64, UInt64 } from "node-opcua-basic-types";
5
+ import type { Namespace, UAObject } from "node-opcua-address-space";
6
+
7
+ function getDADiscreteTypeFolder(namespace: Namespace, parentFolder: UAObject): UAObject {
8
+ const name = "Simulation_DA_DiscreteType";
9
+ const nodeId = "s=Simulation_DA_DiscreteType";
10
+
11
+ let node = parentFolder.getFolderElementByName(name);
12
+ if (!node) {
13
+ node = namespace.addObject({
14
+ organizedBy: parentFolder,
15
+ typeDefinition: "FolderType",
16
+ browseName: name,
17
+ nodeId
18
+ });
19
+ }
20
+ return node as UAObject;
21
+ }
22
+
23
+ export function addTwoStateDiscreteVariables(namespace: Namespace, parentFolder: UAObject): void {
24
+ const DADiscreteTypeFolder = getDADiscreteTypeFolder(namespace, parentFolder);
25
+
26
+ const twoStateDiscrete001 = namespace.addTwoStateDiscrete({
27
+ organizedBy: DADiscreteTypeFolder,
28
+ nodeId: "s=TwoStateDiscrete001",
29
+ browseName: "TwoStateDiscrete001",
30
+ trueState: "Enabled",
31
+ falseState: "Disabled"
32
+ });
33
+
34
+ const twoStateDiscrete002 = namespace.addTwoStateDiscrete({
35
+ organizedBy: DADiscreteTypeFolder,
36
+ nodeId: "s=TwoStateDiscrete002",
37
+ browseName: "TwoStateDiscrete002",
38
+ trueState: "On",
39
+ falseState: "Off",
40
+ optionals: ["TransitionTime", "EffectiveDisplayName"]
41
+ });
42
+
43
+ const twoStateDiscrete003 = namespace.addTwoStateDiscrete({
44
+ browseName: "twoStateDiscrete003",
45
+ nodeId: "s=TwoStateDiscrete003",
46
+ optionals: ["TransitionTime"],
47
+ isTrueSubStateOf: twoStateDiscrete002
48
+ });
49
+
50
+ const twoStateDiscrete004 = namespace.addTwoStateDiscrete({
51
+ organizedBy: DADiscreteTypeFolder,
52
+ nodeId: "s=TwoStateDiscrete004",
53
+ browseName: "TwoStateDiscrete004",
54
+ trueState: "InProgress",
55
+ falseState: "Stopped"
56
+ });
57
+
58
+ const twoStateDiscrete005 = namespace.addTwoStateDiscrete({
59
+ organizedBy: DADiscreteTypeFolder,
60
+ nodeId: "s=TwoStateDiscrete005",
61
+ browseName: "TwoStateDiscrete005",
62
+ trueState: "InProgress",
63
+ falseState: "Stopped"
64
+ });
65
+
66
+ twoStateDiscrete001.setValueFromSource({ dataType: "Boolean", value: false });
67
+ twoStateDiscrete002.setValueFromSource({ dataType: "Boolean", value: false });
68
+ twoStateDiscrete003.setValueFromSource({ dataType: "Boolean", value: false });
69
+ twoStateDiscrete004.setValueFromSource({ dataType: "Boolean", value: false });
70
+ twoStateDiscrete005.setValueFromSource({ dataType: "Boolean", value: false });
71
+ }
72
+
73
+ export function addMultiStateDiscreteVariable(namespace: Namespace, parentFolder: UAObject): void {
74
+ const DADiscreteTypeFolder = getDADiscreteTypeFolder(namespace, parentFolder);
75
+
76
+ for (let i = 1; i <= 5; i++) {
77
+ const name = `MultiStateDiscrete${i.toString().padStart(3, "0")}`;
78
+ namespace.addMultiStateDiscrete({
79
+ organizedBy: DADiscreteTypeFolder,
80
+ nodeId: `s=${name}`,
81
+ browseName: name,
82
+ enumStrings: ["Red", "Orange", "Green"],
83
+ value: 1 // Orange
84
+ });
85
+ }
86
+ }
87
+
88
+ export function addMultiStateValueDiscreteVariables(namespaceDemo: Namespace, parentFolder: UAObject): void {
89
+ const multistateValueDiscreteTypeFolder = namespaceDemo.addObject({
90
+ organizedBy: parentFolder,
91
+ typeDefinition: "FolderType",
92
+ browseName: "Simulation_DA_MultiStateValueDiscreteType",
93
+ nodeId: "s=Simulation_DA_MultiStateValueDiscreteType"
94
+ });
95
+
96
+ function _add_multi_state_variable(
97
+ folder: UAObject,
98
+ dataType: string,
99
+ _value: number | UInt64 | Int64,
100
+ enumValues: Record<string, number>
101
+ ) {
102
+ const name = `${dataType}MultiStateValueDiscrete`;
103
+ const nodeId = `s=${name}`;
104
+
105
+ namespaceDemo.addMultiStateValueDiscrete({
106
+ organizedBy: folder,
107
+ browseName: name,
108
+ nodeId,
109
+ dataType,
110
+ enumValues,
111
+ value: 0x0000 // Zero
112
+ });
113
+ }
114
+
115
+ const enumValueUnsigned = { Zero: 0x00, One: 0x01, Two: 0x02, Three: 0x03, Four: 0x04, Five: 0x05, Six: 0x06, Seven: 0x07 };
116
+ const enumValueSigned = { MinusOne: -1, MinusTwo: -2, MinusThree: -3, ...enumValueUnsigned };
117
+
118
+ const data = [
119
+ { dataType: "Int16", value: -1, enumValue: enumValueSigned },
120
+ { dataType: "UInt16", value: 10, enumValue: enumValueUnsigned },
121
+ { dataType: "Int32", value: -1, enumValue: enumValueSigned },
122
+ { dataType: "UInt32", value: 100, enumValue: enumValueUnsigned },
123
+ { dataType: "Int64", value: [0, 0], enumValue: enumValueUnsigned },
124
+ { dataType: "UInt64", value: [0, 0], enumValue: enumValueSigned },
125
+ { dataType: "Byte", value: 1, enumValue: enumValueUnsigned },
126
+ { dataType: "SByte", value: -1, enumValue: enumValueSigned }
127
+ ];
128
+ for (const e of data) {
129
+ _add_multi_state_variable(multistateValueDiscreteTypeFolder, e.dataType, e.value, e.enumValue);
130
+ }
131
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Enumeration type and variable for conformance testing.
3
+ */
4
+ import type { Namespace, UAObject } from "node-opcua-address-space";
5
+
6
+ export function addEnumerationVariable(namespaceDemo: Namespace, parentFolder: UAObject): void {
7
+ const addressSpace = namespaceDemo.addressSpace;
8
+
9
+ const myEnumType = namespaceDemo.addEnumerationType({
10
+ browseName: "SimulationEnumerationType",
11
+ enumeration: [
12
+ { value: 1, displayName: "RUNNING" },
13
+ { value: 2, displayName: "BLOCKED" },
14
+ { value: 3, displayName: "IDLE" },
15
+ { value: 4, displayName: "UNDER MAINTENANCE" }
16
+ ]
17
+ });
18
+
19
+ const e = namespaceDemo.addVariable({
20
+ organizedBy: parentFolder,
21
+ propertyOf: (addressSpace.rootFolder.objects.server as unknown as Record<string, unknown>)
22
+ .vendorServerInfos as unknown as UAObject,
23
+ dataType: myEnumType,
24
+ browseName: "RunningState"
25
+ });
26
+ e.writeEnumValue("RUNNING");
27
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Shared helper utilities for conformance testing address space construction.
3
+ */
4
+ import type { Namespace, UAObject, UAVariable } from "node-opcua-address-space";
5
+ import { assert } from "node-opcua-assert";
6
+ import * as ec from "node-opcua-basic-types";
7
+ import { randomString } from "node-opcua-basic-types";
8
+ import { AccessLevelFlag, LocalizedText, makeAccessLevelFlag, QualifiedName } from "node-opcua-data-model";
9
+ import { make_debugLog, make_warningLog } from "node-opcua-debug";
10
+ import { findBuiltInType } from "node-opcua-factory";
11
+ import { buildVariantArray, DataType, Variant, VariantArrayType } from "node-opcua-variant";
12
+
13
+ const debugLog = make_debugLog(__filename);
14
+ const warningLog = make_warningLog(__filename);
15
+
16
+ // ─── Validators & Random Generators ─────────────────────────────────────
17
+
18
+ function defaultValidator(/*value*/) {
19
+ return true;
20
+ }
21
+
22
+ export function getValidatorFuncForType(dataType: DataType): (value: unknown) => boolean {
23
+ const f = (ec as Record<string, unknown>)[`isValid${dataType}`];
24
+ return (f as (value: unknown) => boolean) || defaultValidator;
25
+ }
26
+
27
+ export function getRandomFuncForType(dataType: DataType): () => unknown {
28
+ const dataTypeName = DataType[dataType];
29
+ const f = (ec as Record<string, unknown>)[`random${dataTypeName}`];
30
+
31
+ if (f) {
32
+ return f as () => unknown;
33
+ }
34
+
35
+ switch (dataTypeName) {
36
+ case "Variant":
37
+ return () => {
38
+ return new Variant();
39
+ };
40
+ case "QualifiedName":
41
+ return () => {
42
+ return new QualifiedName({ name: randomString() });
43
+ };
44
+ case "LocalizedText":
45
+ return () => {
46
+ return new LocalizedText({ text: randomString() });
47
+ };
48
+ case "XmlElement":
49
+ return () => {
50
+ const element = randomString();
51
+ const content = randomString();
52
+ return `<${element}>${content}</${element}>`;
53
+ };
54
+ default:
55
+ // c8 ignore next
56
+ throw new Error(`Cannot find random${dataTypeName}() func anywhere`);
57
+ }
58
+ }
59
+
60
+ // ─── DataType Lookup ────────────────────────────────────────────────────
61
+
62
+ export function findDataType(dataTypeName: string): DataType {
63
+ const builtInDataTypeName = findBuiltInType(dataTypeName);
64
+ const dataType = DataType[builtInDataTypeName.name as keyof typeof DataType];
65
+ // c8 ignore next
66
+ if (!dataType) {
67
+ throw new Error(` dataType ${dataTypeName} must exists`);
68
+ }
69
+ return dataType;
70
+ }
71
+
72
+ // ─── Validation ─────────────────────────────────────────────────────────
73
+
74
+ function validate_value_or_array(isArray: boolean, variantValue: unknown, validatorFunc: (v: unknown) => boolean) {
75
+ assert(typeof validatorFunc === "function");
76
+ if (isArray) {
77
+ const arr = variantValue as ArrayLike<unknown>;
78
+ const n = Math.min(10, arr.length);
79
+ for (let i = 0; i < n; i++) {
80
+ // c8 ignore next
81
+ if (!validatorFunc(arr[i])) {
82
+ throw new Error(`default value must be valid for dataType ${variantValue} at index ${i} got ${arr[i]}`);
83
+ }
84
+ }
85
+ } else {
86
+ // c8 ignore next
87
+ if (!validatorFunc(variantValue)) {
88
+ throw new Error(`default value must be valid for dataType ${variantValue}`);
89
+ }
90
+ }
91
+ }
92
+
93
+ // ─── Variant Construction ───────────────────────────────────────────────
94
+
95
+ export function makeVariant(dataTypeName: string, arrayType: VariantArrayType, dimensions: number[] | null, current_value: unknown) {
96
+ const dataType = findDataType(dataTypeName);
97
+ const validatorFunc = getValidatorFuncForType(dataType);
98
+ const isArray = arrayType === VariantArrayType.Array || arrayType === VariantArrayType.Matrix;
99
+ validate_value_or_array(isArray, current_value, validatorFunc);
100
+
101
+ return new Variant({
102
+ dataType,
103
+ arrayType,
104
+ dimensions,
105
+ value: current_value
106
+ });
107
+ }
108
+
109
+ // ─── Generic Variable Helpers ───────────────────────────────────────────
110
+
111
+ export function addVariable(
112
+ namespace: Namespace,
113
+ parent: UAObject,
114
+ varName: string,
115
+ dataTypeName: string,
116
+ current_value: unknown,
117
+ valueRank: number,
118
+ arrayDimensions: number[] | null,
119
+ extra_name: string
120
+ ): UAVariable {
121
+ assert(typeof extra_name === "string");
122
+
123
+ const arrayType = valueRank <= 0 ? VariantArrayType.Scalar : valueRank === 1 ? VariantArrayType.Array : VariantArrayType.Matrix;
124
+ const variant = makeVariant(dataTypeName, arrayType, arrayDimensions, current_value);
125
+ const name = varName + extra_name;
126
+ const nodeId = `${parent.nodeId.toString()}_${varName}${extra_name}`;
127
+ const placeholder = { variant };
128
+
129
+ const variable = namespace.addVariable({
130
+ browseName: name,
131
+ componentOf: parent,
132
+ dataType: varName,
133
+ description: { locale: "en", text: name },
134
+ nodeId,
135
+ valueRank,
136
+ arrayDimensions,
137
+ accessLevel: makeAccessLevelFlag("CurrentRead | CurrentWrite"),
138
+ userAccessLevel: makeAccessLevelFlag("CurrentRead | CurrentWrite"),
139
+ value: variant
140
+ });
141
+ (variable as unknown as Record<string, unknown>)._backdoor_placeholder = placeholder;
142
+ return variable;
143
+ }
144
+
145
+ export function addScalarVariable(
146
+ namespace: Namespace,
147
+ parent: UAObject,
148
+ name: string,
149
+ realType: string,
150
+ default_value: unknown,
151
+ extra_name: string
152
+ ) {
153
+ assert(typeof extra_name === "string");
154
+ const initialValue = typeof default_value === "function" ? default_value() : default_value;
155
+ const variable = addVariable(namespace, parent, name, realType, initialValue, -1, null, extra_name);
156
+ assert(variable.valueRank === -1);
157
+ assert(variable.accessLevel === AccessLevelFlag.CurrentRead + AccessLevelFlag.CurrentWrite);
158
+ assert(variable.userAccessLevel === AccessLevelFlag.CurrentRead + AccessLevelFlag.CurrentWrite);
159
+ assert(variable.historizing === false);
160
+ return variable;
161
+ }
162
+
163
+ export function addArrayVariable(
164
+ namespace: Namespace,
165
+ parent: UAObject,
166
+ dataTypeName: string,
167
+ default_value: unknown,
168
+ realTypeName: string,
169
+ arrayLength: number,
170
+ extra_name: string
171
+ ): void {
172
+ assert(typeof dataTypeName === "string");
173
+ assert(typeof realTypeName === "string");
174
+
175
+ // c8 ignore next
176
+ if (!DataType[realTypeName as keyof typeof DataType]) {
177
+ warningLog("dataTypeName", dataTypeName);
178
+ warningLog("realTypeName", realTypeName);
179
+ }
180
+
181
+ assert(DataType[realTypeName as keyof typeof DataType], " expecting a valid real type");
182
+ arrayLength = arrayLength || 10;
183
+
184
+ const local_defaultValue = typeof default_value === "function" ? default_value() : default_value;
185
+ const current_value = buildVariantArray(DataType[realTypeName as keyof typeof DataType], arrayLength, local_defaultValue);
186
+ const variable = addVariable(namespace, parent, dataTypeName, realTypeName, current_value, 1, null, extra_name);
187
+
188
+ assert(variable.valueRank === 1);
189
+ assert(variable.accessLevel === AccessLevelFlag.CurrentRead + AccessLevelFlag.CurrentWrite);
190
+ assert(variable.userAccessLevel === AccessLevelFlag.CurrentRead + AccessLevelFlag.CurrentWrite);
191
+ assert(variable.historizing === false);
192
+ }
193
+
194
+ export function addMultiDimensionalArrayVariable(
195
+ namespace: Namespace,
196
+ parent: UAObject,
197
+ dataTypeName: string,
198
+ default_value: unknown,
199
+ realTypeName: string,
200
+ nbRows: number,
201
+ nbCols: number,
202
+ extra_name: string
203
+ ): void {
204
+ assert(typeof dataTypeName === "string");
205
+ assert(typeof realTypeName === "string");
206
+
207
+ // c8 ignore next
208
+ if (!DataType[realTypeName as keyof typeof DataType]) {
209
+ debugLog("dataTypeName", dataTypeName);
210
+ debugLog("realTypeName", realTypeName);
211
+ }
212
+
213
+ assert(DataType[realTypeName as keyof typeof DataType], " expecting a valid real type");
214
+
215
+ nbRows = nbRows || 6;
216
+ nbCols = nbCols || 2;
217
+
218
+ const local_defaultValue = typeof default_value === "function" ? default_value() : default_value;
219
+ const current_value = buildVariantArray(DataType[realTypeName as keyof typeof DataType], nbRows * nbCols, local_defaultValue);
220
+ const variable = addVariable(namespace, parent, dataTypeName, realTypeName, current_value, 2, [nbRows, nbCols], extra_name);
221
+
222
+ assert(variable.valueRank === 2);
223
+ assert(variable.accessLevel === AccessLevelFlag.CurrentRead + AccessLevelFlag.CurrentWrite);
224
+ assert(variable.userAccessLevel === AccessLevelFlag.CurrentRead + AccessLevelFlag.CurrentWrite);
225
+ assert(variable.historizing === false);
226
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Mass variables: 100 items per supported data-type.
3
+ */
4
+ import type { Namespace, UAObject } from "node-opcua-address-space";
5
+
6
+ import { addVariable } from "./helpers";
7
+ import { typeAndDefaultValue } from "./type_defaults";
8
+
9
+ function addMassVariablesOfType(
10
+ namespace: Namespace,
11
+ parent: UAObject,
12
+ dataTypeName: string,
13
+ default_value: unknown,
14
+ realType: string
15
+ ): void {
16
+ const nodeName = `Scalar_Mass_${dataTypeName}`;
17
+
18
+ const scalarMass_Type = namespace.addObject({
19
+ browseName: nodeName,
20
+ description: "This folder will contain 100 items per supported data-type.",
21
+ nodeId: `s=${nodeName}`,
22
+ organizedBy: parent
23
+ });
24
+ for (let i = 0; i <= 99; i++) {
25
+ const extra_name = `_${i.toString().padStart(2, "0")}`;
26
+ const local_defaultValue = typeof default_value === "function" ? default_value() : default_value;
27
+ addVariable(namespace, scalarMass_Type, dataTypeName, realType, local_defaultValue, -1, null, extra_name);
28
+ }
29
+ }
30
+
31
+ export function addMassVariables(namespace: Namespace, scalarFolder: UAObject): void {
32
+ const scalarMass = namespace.addFolder(scalarFolder, {
33
+ browseName: "Scalar_Mass",
34
+ description: "This folder will contain 100 items per supported data-type.",
35
+ nodeId: "s=Scalar_Mass"
36
+ });
37
+
38
+ for (const e of typeAndDefaultValue) {
39
+ const realType = e.realType || e.type;
40
+ addMassVariablesOfType(namespace, scalarMass, e.type, e.defaultValue, realType);
41
+ }
42
+ }