@vitormnm/node-red-simple-opcua 1.0.2 → 1.1.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.
- package/client/lib/opcua-client-browser.js +291 -0
- package/client/lib/opcua-client-read-service.js +16 -0
- package/client/lib/opcua-client-subscription-id-service.js +25 -0
- package/client/lib/opcua-client-subscription-service.js +171 -0
- package/client/lib/opcua-client-write-service.js +53 -0
- package/client/opcua-client-config.html +80 -0
- package/client/opcua-client-config.js +159 -0
- package/client/opcua-client-utils.js +320 -0
- package/client/opcua-client.html +1225 -0
- package/client/opcua-client.js +380 -0
- package/object.json +65 -0
- package/package.json +4 -5
- package/server/lib/opcua-address-space-alarm.js +341 -0
- package/server/lib/opcua-address-space-builder.js +1456 -0
- package/server/lib/opcua-config.js +546 -0
- package/server/lib/opcua-constants.js +109 -0
- package/server/lib/opcua-server-events-child.js +140 -0
- package/server/lib/opcua-server-methods.js +198 -0
- package/server/lib/opcua-server-runtime-child.js +729 -0
- package/server/lib/opcua-server-runtime.js +311 -0
- package/server/lib/opcua-server-status-child.js +188 -0
- package/server/lib/server-node-utils.js +16 -0
- package/server/opcua-server-io.html +347 -0
- package/server/opcua-server-io.js +463 -0
- package/server/opcua-server-registry.js +270 -0
- package/server/opcua-server.css +265 -0
- package/server/opcua-server.html +1548 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
OPCUAClient,
|
|
5
|
+
getMethodArgumentDefinition,
|
|
6
|
+
resolveMethodObjectId,
|
|
7
|
+
resolveSecurityMode,
|
|
8
|
+
resolveSecurityPolicy
|
|
9
|
+
} = require("./opcua-client-utils");
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
browseNode,
|
|
13
|
+
ROOT_NODE_ID
|
|
14
|
+
} = require("./lib/opcua-client-browser");
|
|
15
|
+
|
|
16
|
+
module.exports = function (RED) {
|
|
17
|
+
function OpcUaClientConfigNode(config) {
|
|
18
|
+
RED.nodes.createNode(this, config);
|
|
19
|
+
const node = this;
|
|
20
|
+
|
|
21
|
+
node.name = (config.name || "").trim();
|
|
22
|
+
node.endpoint = (config.endpoint || "").trim();
|
|
23
|
+
node.securityPolicy = config.securityPolicy || "None";
|
|
24
|
+
node.securityMode = config.securityMode || "None";
|
|
25
|
+
node.authType = config.authType || "anonymous";
|
|
26
|
+
node.client = null;
|
|
27
|
+
node.session = null;
|
|
28
|
+
node.connectPromise = null;
|
|
29
|
+
node.methodObjectIdCache = new Map();
|
|
30
|
+
node.methodDefinitionCache = new Map();
|
|
31
|
+
|
|
32
|
+
node.on("close", function (done) {
|
|
33
|
+
node.closeConnection().then(() => done(), done);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
OpcUaClientConfigNode.prototype.getSession = async function () {
|
|
38
|
+
if (this.session) {
|
|
39
|
+
return this.session;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (this.connectPromise) {
|
|
43
|
+
return this.connectPromise;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!this.endpoint) {
|
|
47
|
+
throw new Error("OPC UA endpoint is not configured");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.connectPromise = (async () => {
|
|
51
|
+
const client = OPCUAClient.create({
|
|
52
|
+
endpointMustExist: false,
|
|
53
|
+
keepSessionAlive: true,
|
|
54
|
+
securityMode: resolveSecurityMode(this.securityMode),
|
|
55
|
+
securityPolicy: resolveSecurityPolicy(this.securityPolicy)
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
await client.connect(this.endpoint);
|
|
60
|
+
|
|
61
|
+
const credentials = this.credentials || {};
|
|
62
|
+
const session = this.authType === "username"
|
|
63
|
+
? await client.createSession({
|
|
64
|
+
userName: credentials.username || "",
|
|
65
|
+
password: credentials.password || ""
|
|
66
|
+
})
|
|
67
|
+
: await client.createSession();
|
|
68
|
+
|
|
69
|
+
session.on("session_closed", () => {
|
|
70
|
+
this.session = null;
|
|
71
|
+
this.methodObjectIdCache.clear();
|
|
72
|
+
this.methodDefinitionCache.clear();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.client = client;
|
|
76
|
+
this.session = session;
|
|
77
|
+
return session;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
try {
|
|
80
|
+
await client.disconnect();
|
|
81
|
+
} catch (disconnectError) {
|
|
82
|
+
// Ignore secondary disconnect failures after a failed connect/createSession.
|
|
83
|
+
}
|
|
84
|
+
this.client = null;
|
|
85
|
+
this.session = null;
|
|
86
|
+
throw error;
|
|
87
|
+
} finally {
|
|
88
|
+
this.connectPromise = null;
|
|
89
|
+
}
|
|
90
|
+
})();
|
|
91
|
+
|
|
92
|
+
return this.connectPromise;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
OpcUaClientConfigNode.prototype.resolveMethodObjectId = async function (session, methodNodeId) {
|
|
96
|
+
return resolveMethodObjectId(session, methodNodeId, this.methodObjectIdCache);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
OpcUaClientConfigNode.prototype.getMethodArgumentDefinition = async function (session, methodNodeId) {
|
|
100
|
+
return getMethodArgumentDefinition(session, methodNodeId, this.methodDefinitionCache);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
OpcUaClientConfigNode.prototype.closeConnection = async function () {
|
|
104
|
+
const session = this.session;
|
|
105
|
+
const client = this.client;
|
|
106
|
+
|
|
107
|
+
this.session = null;
|
|
108
|
+
this.client = null;
|
|
109
|
+
this.connectPromise = null;
|
|
110
|
+
this.methodObjectIdCache.clear();
|
|
111
|
+
this.methodDefinitionCache.clear();
|
|
112
|
+
|
|
113
|
+
if (session) {
|
|
114
|
+
try {
|
|
115
|
+
await session.close();
|
|
116
|
+
} catch (error) {
|
|
117
|
+
// Ignore close errors during shutdown.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (client) {
|
|
122
|
+
try {
|
|
123
|
+
await client.disconnect();
|
|
124
|
+
} catch (error) {
|
|
125
|
+
// Ignore disconnect errors during shutdown.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
RED.nodes.registerType("opcua-client-config", OpcUaClientConfigNode, {
|
|
131
|
+
credentials: {
|
|
132
|
+
username: { type: "text" },
|
|
133
|
+
password: { type: "password" }
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
RED.httpAdmin.get("/opcua-client-config/:id/browse", RED.auth.needsPermission("flows.read"), async function (req, res) {
|
|
138
|
+
try {
|
|
139
|
+
const configNode = RED.nodes.getNode(req.params.id);
|
|
140
|
+
|
|
141
|
+
if (!configNode) {
|
|
142
|
+
res.status(404).json({ error: "OPC UA client configuration not found" });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const payload = await browseForEditor(configNode, req.query.nodeId);
|
|
147
|
+
res.json(payload);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
res.status(500).json({ error: error.message });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
async function browseForEditor(configNode, nodeId) {
|
|
155
|
+
const session = await configNode.getSession();
|
|
156
|
+
return browseNode(session, {
|
|
157
|
+
nodeID: nodeId || ROOT_NODE_ID
|
|
158
|
+
});
|
|
159
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
AttributeIds,
|
|
5
|
+
BrowseDirection,
|
|
6
|
+
coerceNodeId,
|
|
7
|
+
DataType,
|
|
8
|
+
MessageSecurityMode,
|
|
9
|
+
NodeClass,
|
|
10
|
+
OPCUAClient,
|
|
11
|
+
SecurityPolicy,
|
|
12
|
+
TimestampsToReturn,
|
|
13
|
+
Variant
|
|
14
|
+
} = require("node-opcua");
|
|
15
|
+
|
|
16
|
+
function resolveSecurityPolicy(name) {
|
|
17
|
+
return SecurityPolicy[name] || SecurityPolicy.None;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveSecurityMode(name) {
|
|
21
|
+
return MessageSecurityMode[name] || MessageSecurityMode.None;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeTypeName(type) {
|
|
25
|
+
const raw = String(type || "").trim();
|
|
26
|
+
if (!raw) {
|
|
27
|
+
return "";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const aliases = {
|
|
31
|
+
Bool: "Boolean",
|
|
32
|
+
Boolean: "Boolean",
|
|
33
|
+
Byte: "Byte",
|
|
34
|
+
SByte: "SByte",
|
|
35
|
+
Int: "Int32",
|
|
36
|
+
Int16: "Int16",
|
|
37
|
+
Int32: "Int32",
|
|
38
|
+
Int64: "Int64",
|
|
39
|
+
UInt16: "UInt16",
|
|
40
|
+
UInt32: "UInt32",
|
|
41
|
+
UInt64: "UInt64",
|
|
42
|
+
Float: "Float",
|
|
43
|
+
Double: "Double",
|
|
44
|
+
String: "String",
|
|
45
|
+
DateTime: "DateTime"
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return aliases[raw] || raw;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function inferTypeName(value) {
|
|
52
|
+
if (typeof value === "boolean") {
|
|
53
|
+
return "Boolean";
|
|
54
|
+
}
|
|
55
|
+
if (typeof value === "string") {
|
|
56
|
+
return "String";
|
|
57
|
+
}
|
|
58
|
+
if (typeof value === "number") {
|
|
59
|
+
return Number.isInteger(value) ? "Int32" : "Double";
|
|
60
|
+
}
|
|
61
|
+
if (value instanceof Date) {
|
|
62
|
+
return "DateTime";
|
|
63
|
+
}
|
|
64
|
+
if (Buffer.isBuffer(value)) {
|
|
65
|
+
return "ByteString";
|
|
66
|
+
}
|
|
67
|
+
return "String";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function coerceValue(value, typeName) {
|
|
71
|
+
switch (typeName) {
|
|
72
|
+
case "Boolean":
|
|
73
|
+
if (typeof value === "string") {
|
|
74
|
+
return value.trim().toLowerCase() === "true";
|
|
75
|
+
}
|
|
76
|
+
return Boolean(value);
|
|
77
|
+
case "Byte":
|
|
78
|
+
case "SByte":
|
|
79
|
+
case "Int16":
|
|
80
|
+
case "Int32":
|
|
81
|
+
case "UInt16":
|
|
82
|
+
case "UInt32":
|
|
83
|
+
return Number.parseInt(value, 10);
|
|
84
|
+
case "Int64":
|
|
85
|
+
case "UInt64":
|
|
86
|
+
return BigInt(value);
|
|
87
|
+
case "Float":
|
|
88
|
+
case "Double":
|
|
89
|
+
return Number.parseFloat(value);
|
|
90
|
+
case "DateTime":
|
|
91
|
+
return value instanceof Date ? value : new Date(value);
|
|
92
|
+
case "String":
|
|
93
|
+
return value === undefined || value === null ? "" : String(value);
|
|
94
|
+
default:
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function buildVariantFromItem(item, fallbackTypeName) {
|
|
100
|
+
const typeName = normalizeTypeName(item.type || fallbackTypeName || inferTypeName(item.value));
|
|
101
|
+
const dataType = DataType[typeName];
|
|
102
|
+
|
|
103
|
+
if (dataType === undefined) {
|
|
104
|
+
throw new Error("Unsupported OPC UA data type: " + typeName);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return new Variant({
|
|
108
|
+
dataType,
|
|
109
|
+
value: coerceValue(item.value, typeName)
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function ensureArrayPayload(msg, contextName) {
|
|
114
|
+
const payload = msg ? msg.payload : undefined;
|
|
115
|
+
|
|
116
|
+
if (!Array.isArray(payload) || payload.length === 0) {
|
|
117
|
+
throw new Error(contextName + " expects msg.payload as a non-empty array");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return payload;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function resolveNodeId(item) {
|
|
124
|
+
const nodeId = item && (item.nodeID || item.nodeId);
|
|
125
|
+
if (!nodeId || !String(nodeId).trim()) {
|
|
126
|
+
throw new Error("Each item must contain nodeID");
|
|
127
|
+
}
|
|
128
|
+
return String(nodeId).trim();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function resolveName(item, fallback) {
|
|
132
|
+
return String(item && item.name ? item.name : fallback || "").trim();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function statusCodeToString(statusCode) {
|
|
136
|
+
if (!statusCode) {
|
|
137
|
+
return "Unknown";
|
|
138
|
+
}
|
|
139
|
+
return statusCode.name || statusCode.toString();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function timestampToIso(value) {
|
|
143
|
+
return value instanceof Date && !Number.isNaN(value.getTime())
|
|
144
|
+
? value.toISOString()
|
|
145
|
+
: null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function variantTypeToName(variant) {
|
|
149
|
+
if (!variant || variant.dataType === undefined || variant.dataType === null) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
return DataType[variant.dataType] || String(variant.dataType);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function dataValueToItemResult(item, dataValue) {
|
|
156
|
+
const variant = dataValue && dataValue.value ? dataValue.value : null;
|
|
157
|
+
return {
|
|
158
|
+
name: resolveName(item, resolveNodeId(item)),
|
|
159
|
+
nodeID: resolveNodeId(item),
|
|
160
|
+
value: variant ? variant.value : null,
|
|
161
|
+
type: variantTypeToName(variant),
|
|
162
|
+
status: statusCodeToString(dataValue && dataValue.statusCode),
|
|
163
|
+
sourceTimestamp: timestampToIso(dataValue && dataValue.sourceTimestamp),
|
|
164
|
+
serverTimestamp: timestampToIso(dataValue && dataValue.serverTimestamp)
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function dataValueToItemResultEvent(item, eventFields, session) {
|
|
169
|
+
const variant = eventFields && eventFields.value ? eventFields.value : null;
|
|
170
|
+
|
|
171
|
+
// lê BrowseName do tipo
|
|
172
|
+
const dv = await session.read({
|
|
173
|
+
nodeId: eventFields[1].value,
|
|
174
|
+
attributeId: AttributeIds.BrowseName
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const eventTypeName = dv.value.value.name;
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
eventId: eventFields[0].value,
|
|
181
|
+
eventType: eventFields[1].value,
|
|
182
|
+
eventTypeName: eventTypeName,
|
|
183
|
+
sourceNode: eventFields[2].value.toString(),
|
|
184
|
+
sourceName: eventFields[3].value,
|
|
185
|
+
message: eventFields[4].value?.text,
|
|
186
|
+
severity: eventFields[5].value,
|
|
187
|
+
active: eventFields[6].value?.text, // Active / Inactive
|
|
188
|
+
AckedState: eventFields[7].value?.text, // Active / Inactive
|
|
189
|
+
ConfirmedState: eventFields[8].value?.text, // Active / Inactive
|
|
190
|
+
time: eventFields[9].value
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function callResultToItemResult(item, callResult, argumentDefinition) {
|
|
195
|
+
const methodId = resolveMethodNodeId(item);
|
|
196
|
+
const outputDefinitions = argumentDefinition && Array.isArray(argumentDefinition.outputArguments)
|
|
197
|
+
? argumentDefinition.outputArguments
|
|
198
|
+
: [];
|
|
199
|
+
const outputArguments = Array.isArray(callResult.outputArguments)
|
|
200
|
+
? callResult.outputArguments
|
|
201
|
+
: [];
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
name: resolveName(item, methodId),
|
|
205
|
+
nodeID: methodId,
|
|
206
|
+
status: statusCodeToString(callResult.statusCode),
|
|
207
|
+
outputs: outputArguments.map((variant, index) => ({
|
|
208
|
+
name: outputDefinitions[index] && outputDefinitions[index].name
|
|
209
|
+
? String(outputDefinitions[index].name)
|
|
210
|
+
: "output" + (index + 1),
|
|
211
|
+
type: variantTypeToName(variant),
|
|
212
|
+
value: variant ? variant.value : null
|
|
213
|
+
}))
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function resolveMethodNodeId(item) {
|
|
218
|
+
const methodId = item && (item.methodID || item.methodId || item.nodeID || item.nodeId);
|
|
219
|
+
if (!methodId || !String(methodId).trim()) {
|
|
220
|
+
throw new Error("Each method item must contain methodId or nodeID");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return String(methodId).trim();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function resolveMethodObjectId(session, methodNodeId, cache) {
|
|
227
|
+
const cacheKey = String(methodNodeId);
|
|
228
|
+
if (cache && cache.has(cacheKey)) {
|
|
229
|
+
return cache.get(cacheKey);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const browseResult = await session.browse({
|
|
233
|
+
nodeId: methodNodeId,
|
|
234
|
+
browseDirection: BrowseDirection.Inverse,
|
|
235
|
+
includeSubtypes: true,
|
|
236
|
+
resultMask: 63
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const references = browseResult && Array.isArray(browseResult.references)
|
|
240
|
+
? browseResult.references
|
|
241
|
+
: [];
|
|
242
|
+
|
|
243
|
+
const objectReferences = references.filter((reference) => {
|
|
244
|
+
const nodeClassName = resolveNodeClassName(reference.nodeClass);
|
|
245
|
+
return nodeClassName === "Object" || nodeClassName === "ObjectType";
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const parentReference = objectReferences.find((reference) => isComponentReference(reference.referenceTypeId))
|
|
249
|
+
|| objectReferences[0]
|
|
250
|
+
|| references[0];
|
|
251
|
+
|
|
252
|
+
if (!parentReference) {
|
|
253
|
+
throw new Error("Unable to resolve parent object for method " + methodNodeId);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const objectId = parentReference.nodeId.toString();
|
|
257
|
+
if (cache) {
|
|
258
|
+
cache.set(cacheKey, objectId);
|
|
259
|
+
}
|
|
260
|
+
return objectId;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function resolveNodeClassName(nodeClass) {
|
|
264
|
+
if (!nodeClass && nodeClass !== 0) {
|
|
265
|
+
return "";
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (typeof nodeClass === "object" && typeof nodeClass.key === "string") {
|
|
269
|
+
return nodeClass.key;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (typeof nodeClass === "string") {
|
|
273
|
+
return nodeClass;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return NodeClass[nodeClass] || String(nodeClass);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function isComponentReference(referenceTypeId) {
|
|
280
|
+
const value = referenceTypeId && typeof referenceTypeId.toString === "function"
|
|
281
|
+
? referenceTypeId.toString()
|
|
282
|
+
: String(referenceTypeId || "");
|
|
283
|
+
|
|
284
|
+
return value === "i=47" || value === "i=49";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function getMethodArgumentDefinition(session, methodNodeId, cache) {
|
|
288
|
+
const cacheKey = String(methodNodeId);
|
|
289
|
+
if (cache && cache.has(cacheKey)) {
|
|
290
|
+
return cache.get(cacheKey);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const definition = await session.getArgumentDefinition(methodNodeId);
|
|
294
|
+
if (cache) {
|
|
295
|
+
cache.set(cacheKey, definition);
|
|
296
|
+
}
|
|
297
|
+
return definition;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
module.exports = {
|
|
301
|
+
AttributeIds,
|
|
302
|
+
coerceNodeId,
|
|
303
|
+
DataType,
|
|
304
|
+
OPCUAClient,
|
|
305
|
+
TimestampsToReturn,
|
|
306
|
+
buildVariantFromItem,
|
|
307
|
+
callResultToItemResult,
|
|
308
|
+
dataValueToItemResult,
|
|
309
|
+
dataValueToItemResultEvent,
|
|
310
|
+
ensureArrayPayload,
|
|
311
|
+
getMethodArgumentDefinition,
|
|
312
|
+
inferTypeName,
|
|
313
|
+
normalizeTypeName,
|
|
314
|
+
resolveMethodObjectId,
|
|
315
|
+
resolveName,
|
|
316
|
+
resolveNodeId,
|
|
317
|
+
resolveSecurityMode,
|
|
318
|
+
resolveSecurityPolicy,
|
|
319
|
+
statusCodeToString
|
|
320
|
+
};
|