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