@vitormnm/node-red-simple-opcua 1.0.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/LICENSE +21 -0
- package/README.md +128 -0
- 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 +38 -0
- package/resources/editorClient.PNG +0 -0
- package/resources/editorServer.PNG +0 -0
- 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 +543 -0
- package/server/lib/opcua-constants.js +106 -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
- package/server/opcua-server.js +143 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
OPCUAServer,
|
|
7
|
+
UserTokenType,
|
|
8
|
+
buildApplicationUri
|
|
9
|
+
} = require("./opcua-constants");
|
|
10
|
+
const { OpcUaAddressSpaceBuilder } = require("./opcua-address-space-builder");
|
|
11
|
+
const { OpcUaServerMethods } = require("./opcua-server-methods");
|
|
12
|
+
|
|
13
|
+
class OpcUaServerRuntime {
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.node = options.node;
|
|
16
|
+
this.registry = options.registry;
|
|
17
|
+
this.id = options.settings.id;
|
|
18
|
+
this.name = options.settings.name;
|
|
19
|
+
this.serverName = options.settings.serverName;
|
|
20
|
+
this.port = options.settings.port;
|
|
21
|
+
this.maxConnections = options.settings.maxConnections;
|
|
22
|
+
this.namespaceUri = options.settings.namespaceUri;
|
|
23
|
+
this.resourcePath = options.settings.resourcePath;
|
|
24
|
+
this.allowAnonymous = options.settings.allowAnonymous;
|
|
25
|
+
this.users = options.settings.users;
|
|
26
|
+
this.securityPolicy = options.settings.securityPolicy;
|
|
27
|
+
this.securityMode = options.settings.securityMode;
|
|
28
|
+
this.treeConfig = options.settings.treeConfig;
|
|
29
|
+
|
|
30
|
+
this.server = null;
|
|
31
|
+
this.namespace = null;
|
|
32
|
+
this.namespaces = new Map();
|
|
33
|
+
this.namespaceDefinitions = new Map();
|
|
34
|
+
this.addressSpaceBuilder = null;
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async start() {
|
|
40
|
+
if (this.server) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.server = new OPCUAServer(this.buildServerOptions());
|
|
45
|
+
await this.server.initialize();
|
|
46
|
+
|
|
47
|
+
const addressSpace = this.server.engine.addressSpace;
|
|
48
|
+
|
|
49
|
+
this.initializeNamespaces(this.treeConfig);
|
|
50
|
+
|
|
51
|
+
this.addressSpaceBuilder = new OpcUaAddressSpaceBuilder({
|
|
52
|
+
namespace: this.namespace,
|
|
53
|
+
namespaces: this.namespaces,
|
|
54
|
+
server: this.server,
|
|
55
|
+
registry: this.registry,
|
|
56
|
+
node: this.node,
|
|
57
|
+
serverName: this.serverName,
|
|
58
|
+
addressSpace: this.addressSpace
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
this.addressSpaceBuilder.rebuild(this.treeConfig);
|
|
63
|
+
await this.server.start();
|
|
64
|
+
this.registry.registerServer(this);
|
|
65
|
+
|
|
66
|
+
//Methods
|
|
67
|
+
const ServerMethods = new OpcUaServerMethods({
|
|
68
|
+
addressSpace: addressSpace,
|
|
69
|
+
registry: this.registry,
|
|
70
|
+
node: this.node
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
ServerMethods.start();
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async stop() {
|
|
79
|
+
if (!this.server) {
|
|
80
|
+
this.node.status({ fill: "grey", shape: "ring", text: "stopped" });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
if (this.addressSpaceBuilder) {
|
|
86
|
+
this.addressSpaceBuilder.clearDynamicNodes();
|
|
87
|
+
this.addressSpaceBuilder.variableStore.clear();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await this.server.shutdown(1000);
|
|
91
|
+
} finally {
|
|
92
|
+
this.registry.unregisterServer(this.id);
|
|
93
|
+
this.addressSpaceBuilder = null;
|
|
94
|
+
this.namespace = null;
|
|
95
|
+
this.namespaces = new Map();
|
|
96
|
+
this.namespaceDefinitions = new Map();
|
|
97
|
+
this.server = null;
|
|
98
|
+
this.node.status({ fill: "grey", shape: "ring", text: "stopped" });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
ensureReady() {
|
|
103
|
+
if (!this.server || !this.namespace || !this.addressSpaceBuilder) {
|
|
104
|
+
throw new Error("OPC UA server is not available");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async updateTree(treeConfig) {
|
|
111
|
+
this.ensureReady();
|
|
112
|
+
this.syncNamespaces(treeConfig);
|
|
113
|
+
this.treeConfig = treeConfig;
|
|
114
|
+
this.addressSpaceBuilder.sync(treeConfig);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
readValueByPath(path) {
|
|
118
|
+
this.ensureReady();
|
|
119
|
+
return this.addressSpaceBuilder.readValueByPath(path);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
readValueByNodeId(nodeId) {
|
|
123
|
+
this.ensureReady();
|
|
124
|
+
return this.addressSpaceBuilder.readValueByNodeId(nodeId);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
readValue(identifierType, identifier) {
|
|
128
|
+
this.ensureReady();
|
|
129
|
+
return this.addressSpaceBuilder.readValue(identifierType, identifier);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
writeEventByPath(valuesPayload) {
|
|
133
|
+
this.ensureReady();
|
|
134
|
+
return this.addressSpaceBuilder.eventValueByPath(valuesPayload);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
writeValueByPath(path, value) {
|
|
138
|
+
this.ensureReady();
|
|
139
|
+
return this.addressSpaceBuilder.writeValueByPath(path, value);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
writeValueByNodeId(nodeId, value) {
|
|
143
|
+
this.ensureReady();
|
|
144
|
+
return this.addressSpaceBuilder.writeValueByNodeId(nodeId, value);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
writeValue(identifierType, identifier, value) {
|
|
148
|
+
this.ensureReady();
|
|
149
|
+
return this.addressSpaceBuilder.writeValue(identifierType, identifier, value);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getEndpointUrl() {
|
|
153
|
+
if (!this.server || !Array.isArray(this.server.endpoints)) {
|
|
154
|
+
return "";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for (let index = 0; index < this.server.endpoints.length; index += 1) {
|
|
158
|
+
const endpoint = this.server.endpoints[index];
|
|
159
|
+
if (!endpoint || typeof endpoint.endpointDescriptions !== "function") {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const descriptions = endpoint.endpointDescriptions();
|
|
164
|
+
if (Array.isArray(descriptions) && descriptions.length && descriptions[0].endpointUrl) {
|
|
165
|
+
return descriptions[0].endpointUrl;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return "opc.tcp://localhost:" + this.port + this.resourcePath;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
buildServerOptions() {
|
|
173
|
+
const activeUsers = Array.isArray(this.users) ? this.users : [];
|
|
174
|
+
const userTokenPolicies = [];
|
|
175
|
+
|
|
176
|
+
if (this.allowAnonymous) {
|
|
177
|
+
userTokenPolicies.push({
|
|
178
|
+
policyId: "anonymous",
|
|
179
|
+
tokenType: UserTokenType.Anonymous
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (activeUsers.length) {
|
|
184
|
+
userTokenPolicies.push({
|
|
185
|
+
policyId: "username",
|
|
186
|
+
tokenType: UserTokenType.UserName
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
port: this.port,
|
|
192
|
+
resourcePath: this.resourcePath,
|
|
193
|
+
buildInfo: {
|
|
194
|
+
productName: "opc-ua-server",
|
|
195
|
+
buildNumber: "1",
|
|
196
|
+
buildDate: new Date()
|
|
197
|
+
},
|
|
198
|
+
// serverCertificateManager: {
|
|
199
|
+
// automaticallyAcceptUnknownCertificate: true
|
|
200
|
+
// },
|
|
201
|
+
serverCapabilities: {
|
|
202
|
+
maxSessions: this.maxConnections
|
|
203
|
+
},
|
|
204
|
+
serverInfo: {
|
|
205
|
+
applicationName: { text: this.serverName },
|
|
206
|
+
applicationUri: buildApplicationUri(this.serverName),
|
|
207
|
+
productUri: "urn:node-red:opc-ua-server"
|
|
208
|
+
},
|
|
209
|
+
securityPolicies: [this.securityPolicy],
|
|
210
|
+
securityModes: [this.securityMode],
|
|
211
|
+
allowAnonymous: this.allowAnonymous,
|
|
212
|
+
userManager: {
|
|
213
|
+
isValidUser: (username, password) => this.isValidUser(username, password)
|
|
214
|
+
},
|
|
215
|
+
userTokenPolicies
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
isValidUser(username, password) {
|
|
220
|
+
return this.users.some((user) => {
|
|
221
|
+
if (user.username !== username) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (user.password && user.password === password) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (user.passwordHash) {
|
|
230
|
+
try {
|
|
231
|
+
return bcrypt.compareSync(password, user.passwordHash);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
this.node.warn("Failed to validate password hash for user " + username + ": " + error.message);
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return false;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
initializeNamespaces(treeConfig) {
|
|
243
|
+
const addressSpace = this.server.engine.addressSpace;
|
|
244
|
+
const configuredNamespaces = this.buildNamespaceDefinitions(treeConfig);
|
|
245
|
+
|
|
246
|
+
this.namespaces = new Map();
|
|
247
|
+
this.namespaceDefinitions = configuredNamespaces;
|
|
248
|
+
|
|
249
|
+
configuredNamespaces.forEach((uri, namespaceId) => {
|
|
250
|
+
const namespace = addressSpace.getNamespace(uri) || addressSpace.registerNamespace(uri);
|
|
251
|
+
this.namespaces.set(namespaceId, namespace);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
this.namespace = this.namespaces.get(2);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
syncNamespaces(treeConfig) {
|
|
258
|
+
const addressSpace = this.server.engine.addressSpace;
|
|
259
|
+
const nextDefinitions = this.buildNamespaceDefinitions(treeConfig);
|
|
260
|
+
|
|
261
|
+
this.namespaceDefinitions.forEach((uri, namespaceId) => {
|
|
262
|
+
const nextUri = nextDefinitions.get(namespaceId);
|
|
263
|
+
if (nextUri && nextUri !== uri) {
|
|
264
|
+
throw new Error("Namespace URI changes require a redeploy: namespace " + namespaceId);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!nextUri) {
|
|
268
|
+
throw new Error("Removing namespaces requires a redeploy: namespace " + namespaceId);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
nextDefinitions.forEach((uri, namespaceId) => {
|
|
273
|
+
if (this.namespaces.has(namespaceId)) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const namespace = addressSpace.getNamespace(uri) || addressSpace.registerNamespace(uri);
|
|
278
|
+
this.namespaces.set(namespaceId, namespace);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
this.namespaceDefinitions = nextDefinitions;
|
|
282
|
+
this.namespace = this.namespaces.get(2);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
buildNamespaceDefinitions(treeConfig) {
|
|
286
|
+
const definitions = new Map();
|
|
287
|
+
const configuredNamespaces = Array.isArray(treeConfig && treeConfig.nameSpaces) ? treeConfig.nameSpaces : [];
|
|
288
|
+
let defaultNamespaceUri = this.namespaceUri;
|
|
289
|
+
|
|
290
|
+
configuredNamespaces.forEach((namespaceConfig) => {
|
|
291
|
+
if (namespaceConfig.id === 2) {
|
|
292
|
+
defaultNamespaceUri = namespaceConfig.name;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
definitions.set(2, defaultNamespaceUri);
|
|
297
|
+
|
|
298
|
+
configuredNamespaces
|
|
299
|
+
.slice()
|
|
300
|
+
.sort((left, right) => left.id - right.id)
|
|
301
|
+
.forEach((namespaceConfig) => {
|
|
302
|
+
definitions.set(namespaceConfig.id, namespaceConfig.name);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return definitions;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
module.exports = {
|
|
310
|
+
OpcUaServerRuntime
|
|
311
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const registry = require("../opcua-server-registry");
|
|
5
|
+
const { resolveRegisteredServer } = require("./server-node-utils");
|
|
6
|
+
|
|
7
|
+
function OpcUaServerStatusNode(node, msg, nodeId) {
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const serverNode = resolveRegisteredServer(node, msg, registry);
|
|
11
|
+
const snapshot = buildServerSnapshot(serverNode);
|
|
12
|
+
|
|
13
|
+
msg.payload = snapshot;
|
|
14
|
+
msg.opcua = msg.opcua || {};
|
|
15
|
+
msg.opcua.server = snapshot.identity.serverRef;
|
|
16
|
+
msg.opcua.endpointUrl = snapshot.endpointUrl;
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
process.send({
|
|
21
|
+
type: "status",
|
|
22
|
+
data: {
|
|
23
|
+
fill: "green",
|
|
24
|
+
shape: "dot",
|
|
25
|
+
text: "sessions " + snapshot.counters.currentSessionCount
|
|
26
|
+
},
|
|
27
|
+
nodeId: nodeId
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
process.send({
|
|
31
|
+
type: "send",
|
|
32
|
+
data: msg,
|
|
33
|
+
nodeId: nodeId
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
} catch (error) {
|
|
38
|
+
//console.error("function OpcUaServerStatusNode" + error);
|
|
39
|
+
//console.error({ fill: "red", shape: "ring", text: "status failed" });
|
|
40
|
+
|
|
41
|
+
process.send({
|
|
42
|
+
type: "status",
|
|
43
|
+
data: {
|
|
44
|
+
fill: "red",
|
|
45
|
+
shape: "ring",
|
|
46
|
+
text: "Status: " + error
|
|
47
|
+
},
|
|
48
|
+
nodeId: nodeId
|
|
49
|
+
});
|
|
50
|
+
// done(error);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function buildServerSnapshot(serverNode) {
|
|
56
|
+
|
|
57
|
+
const server = serverNode.server;
|
|
58
|
+
const primaryEndpoint = Array.isArray(server.endpoints) && server.endpoints.length
|
|
59
|
+
? server.endpoints[0]
|
|
60
|
+
: null;
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
identity: {
|
|
64
|
+
id: serverNode.id || "",
|
|
65
|
+
name: serverNode.name || "",
|
|
66
|
+
serverName: serverNode.serverName || "",
|
|
67
|
+
serverRef: serverNode.name || serverNode.serverName || serverNode.id || ""
|
|
68
|
+
},
|
|
69
|
+
state: safeCall(server, "getServerState", ""),
|
|
70
|
+
endpointUrl: resolveEndpointUrl(serverNode),
|
|
71
|
+
counters: {
|
|
72
|
+
currentChannelCount: safeNumber(server.currentChannelCount, 0),
|
|
73
|
+
currentSessionCount: safeNumber(server.currentSessionCount, 0),
|
|
74
|
+
currentSubscriptionCount: safeNumber(server.currentSubscriptionCount, 0),
|
|
75
|
+
bytesRead: safeNumber(server.bytesRead, 0),
|
|
76
|
+
bytesWritten: safeNumber(server.bytesWritten, 0),
|
|
77
|
+
transactionsCount: safeNumber(server.transactionsCount, 0),
|
|
78
|
+
rejectedRequestsCount: safeNumber(server.rejectedRequestsCount, 0),
|
|
79
|
+
rejectedSessionCount: safeNumber(server.rejectedSessionCount, 0),
|
|
80
|
+
sessionAbortCount: safeNumber(server.sessionAbortCount, 0),
|
|
81
|
+
publishingIntervalCount: safeNumber(server.publishingIntervalCount, 0),
|
|
82
|
+
securityTokenCount: primaryEndpoint ? safeNumber(primaryEndpoint.securityTokenCount, 0) : 0,
|
|
83
|
+
activeChannelCount: primaryEndpoint ? safeNumber(primaryEndpoint.activeChannelCount, 0) : 0
|
|
84
|
+
},
|
|
85
|
+
capabilities: {
|
|
86
|
+
maxAllowedSessionNumber: safeNumber(server.maxAllowedSessionNumber, 0),
|
|
87
|
+
initialized: Boolean(server.initialized),
|
|
88
|
+
auditing: Boolean(server.isAuditing)
|
|
89
|
+
},
|
|
90
|
+
buildInfo: {
|
|
91
|
+
productName: extractText(server.buildInfo && server.buildInfo.productName),
|
|
92
|
+
buildNumber: extractText(server.buildInfo && server.buildInfo.buildNumber),
|
|
93
|
+
buildDate: extractDate(server.buildInfo && server.buildInfo.buildDate)
|
|
94
|
+
},
|
|
95
|
+
serverInfo: {
|
|
96
|
+
applicationUri: extractText(server.serverInfo && server.serverInfo.applicationUri),
|
|
97
|
+
productUri: extractText(server.serverInfo && server.serverInfo.productUri),
|
|
98
|
+
applicationName: extractLocalizedText(server.serverInfo && server.serverInfo.applicationName)
|
|
99
|
+
},
|
|
100
|
+
endpoints: Array.isArray(server.endpoints)
|
|
101
|
+
? server.endpoints.map((endpoint) => buildEndpointSnapshot(endpoint))
|
|
102
|
+
: []
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function buildEndpointSnapshot(endpoint) {
|
|
107
|
+
const descriptions = typeof endpoint.endpointDescriptions === "function"
|
|
108
|
+
? endpoint.endpointDescriptions()
|
|
109
|
+
: [];
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
port: safeNumber(endpoint.port, 0),
|
|
113
|
+
currentChannelCount: safeNumber(endpoint.currentChannelCount, 0),
|
|
114
|
+
activeChannelCount: safeNumber(endpoint.activeChannelCount, 0),
|
|
115
|
+
bytesRead: safeNumber(endpoint.bytesRead, 0),
|
|
116
|
+
bytesWritten: safeNumber(endpoint.bytesWritten, 0),
|
|
117
|
+
transactionsCount: safeNumber(endpoint.transactionsCount, 0),
|
|
118
|
+
securityTokenCount: safeNumber(endpoint.securityTokenCount, 0),
|
|
119
|
+
endpointUrls: Array.isArray(descriptions)
|
|
120
|
+
? descriptions.map((description) => extractText(description && description.endpointUrl)).filter(Boolean)
|
|
121
|
+
: []
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function resolveEndpointUrl(serverNode) {
|
|
126
|
+
if (serverNode.runtime && typeof serverNode.runtime.getEndpointUrl === "function") {
|
|
127
|
+
return serverNode.runtime.getEndpointUrl();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const server = serverNode.server;
|
|
131
|
+
if (!server || !Array.isArray(server.endpoints)) {
|
|
132
|
+
return "";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (let index = 0; index < server.endpoints.length; index += 1) {
|
|
136
|
+
const endpoint = server.endpoints[index];
|
|
137
|
+
if (!endpoint || typeof endpoint.endpointDescriptions !== "function") {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const descriptions = endpoint.endpointDescriptions();
|
|
142
|
+
if (Array.isArray(descriptions) && descriptions.length && descriptions[0].endpointUrl) {
|
|
143
|
+
return descriptions[0].endpointUrl;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return "";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function safeCall(target, methodName, fallback) {
|
|
151
|
+
try {
|
|
152
|
+
if (target && typeof target[methodName] === "function") {
|
|
153
|
+
return target[methodName]();
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
// Ignore and use fallback.
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return fallback;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function safeNumber(value, fallback) {
|
|
163
|
+
return Number.isFinite(Number(value)) ? Number(value) : fallback;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function extractText(value) {
|
|
167
|
+
return value === undefined || value === null ? "" : String(value);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function extractLocalizedText(value) {
|
|
171
|
+
if (value && typeof value.text === "string") {
|
|
172
|
+
return value.text;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return extractText(value);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function extractDate(value) {
|
|
179
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) {
|
|
180
|
+
return value.toISOString();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return "";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
"OpcUaServerStatusNode": OpcUaServerStatusNode
|
|
188
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function resolveRegisteredServer(node, msg, registry) {
|
|
4
|
+
const msgReference = msg && msg.opcua && msg.opcua.server ? msg.opcua.server : "";
|
|
5
|
+
const server = registry.resolveServer(msgReference || node.serverRef);
|
|
6
|
+
|
|
7
|
+
if (!server) {
|
|
8
|
+
throw new Error("No OPC UA server matched the configured reference");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return server;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
resolveRegisteredServer
|
|
16
|
+
};
|